このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
昨日の続き。
最近の言語では、クラスが名前空間で整理されており、
利用側が空間やクラス単位で宣言を取り込むことができる。
C 言語では、関数を使う側のファイルで、
関数のプロトタイプ宣言が含まれたヘッダファイルを、
必要なだけ選択して取り込む必要がある。
そのためには、どの関数がどのヘッダに宣言されているか、
事前にしっておく必要がある。
まあ、片っ端から取り込むことはできるのだが、
それをするとコンパイル時間が増加してしまう。
リンクを読み込む関数の中では、
IPersistFile と IShellLinkW インタフェースを使う。
そのため、これらの宣言を取り込んでおく必要がある。
では、関数の実装直前までの部分を先に書こう。
========== lnkfile.c (1st half) ==========
/* 制御マクロ宣言 */
#define UNICODE /* UNICODE 用プログラム */
#define STRICT /* 厳密な型を利用 */
#define WIN32_LEAN_AND_MEAN /* コンパイルを高速化 */
#define COBJMACROS /* C 言語用 COM マクロを利用 */
/* 基本ヘッダの取り込み */
#include "lnkfile.h"
/* 各種 GUID, CLSID, IID */
#include <shlguid.h>
/* CoInitialize */
#include <objbase.h>
/* IPersistFile */
#include <objidl.h>
/* IShellLinkW */
#include <shobjidl.h>
========== end of lnkfile.c (1st half) ==========
インタフェースの定義は、MSDN ライブラリで確認する。
IPersistFile は objidl.h、IShellLinkW は shobjidl.h。
そして、C 言語では GUID を直接扱う必要があるので、
定義済みの インタフェース ID やクラス ID の宣言が必要。
これらは、shlguid.h に記載されているので取り込む。
更に、COM を使う場合、その初期化と後処理の関数がある。
CoInitialize/CoUninitialize という関数だ。
これ以外にも COM 用の関数が色々存在する。
これらは、objbase.h にあるので取り込んでおく。
では、いよいよ実装を書いてみよう。
========== lnkfile.c (2nd half) ==========
/* 関数の実装 */
HRESULT ShortcutGetTarget(const wchar_t *fileName,
wchar_t *target, unsigned int bufferSize) {
/* C 言語では、変数は全て先に宣言が必要 */
HRESULT result = S_OK;
IShellLinkW *link = NULL;
IPersistFile *pf = NULL;
/* COM を使う場合は CoInitialize で初期化 */
result = CoInitialize(NULL);
if (FAILED(result)) return result;
/* ShellLink オブジェクトを作成し、IShellLinkW のポインタを取得 */
result = CoCreateInstance(&CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (void **)&link);
if (FAILED(result)) goto cleanup;
/* IPersistFile のポインタも取得 */
result = IUnknown_QueryInterface(link,
&IID_IPersistFile, (void **)&pf);
if (FAILED(result)) goto cleanup;
/* IPersistFile#Load を利用して読み込む */
result = IPersistFile_Load(pf, fileName,
STGM_READ | STGM_SHARE_DENY_WRITE);
if (FAILED(result)) goto cleanup;
/* IShellLinkW#GetPath を利用してリンク先ファイル名を得る */
result = IShellLinkW_GetPath(link, target,
bufferSize, NULL, SLGP_UNCPRIORITY | SLGP_RAWPATH);
cleanup:
/* COM ポインタは、IUnknown#Release で解放 */
if (pf) IUnknown_Release(pf);
if (link) IUnknown_Release(link);
/* CoInitialize と CoUninitialize は必ず対に */
CoUninitialize();
return result;
}
========== end of lnkfile.c (2nd half) ==========
C# での実装と比較すると分かりやすい。
C# が裏で色々とやってくれているのがよく分かる。
http://mixi.jp/view_diary.pl?id=166475473&owner_id=2300658
C 言語では、あらゆる型が値型であるため、
インターフェースやクラスなどは、
ポインタとして宣言されることになる。
変数 link は、IShellLinkW * 型だ。
IShellLinkW の実体のメモリ領域を示すアドレスである。
C 言語では生々しいアドレスなのが高級言語との違いだ。
(といっても、物理メモリのアドレスではないが)
最初に CoInitialize を呼び出して COM を初期化する。
どのタイミングでもいいのだが、
COM を使うスレッドでは必ず呼び出す必要がある。
戻り値は HRESULT 型だ。
HRESULT がエラーを示すかどうかは、
FAILED マクロ関数を使って判断できる。
逆に成功を表すのは、SUCCEEDED マクロ関数だ。
C# では、COM のコクラスを、
Shortcut オブジェクトとして宣言して作成した。
C で作成するには、CoCreateInstance 関数を使う。
引数が多いが、重要なのは、CLSID と IID だ。
NULL と CLSCTX_INPROC_SERVER はほぼ固定値である。
CLSID_ShellLink は作成したいコクラスの識別子だ。
この場合、シェルの ShellLink オブジェクトである。
IID_IShellLink は、新しく作成した ShellLink の
インスタンスを操作するための、インタフェースの指定だ。
両方とも、GUID 構造体だが、
関数はポインタを要求するため、
アドレス演算子 & を前につける必要がある。
参照型のある C++ では不要だが、C では必須だ。
COM クラスは、基本的にクラス自身の型を持たないため、
ShellLink 型として扱うことはできないが、
クラスは最低 1 つのインタフェースを実装しているので、
クラスが実装しているインタフェースを指定して操作する。
Java の無名クラスにちょっと似ている。
そのため、インスタンスを作成した後、
それをインタフェース型として受け取る必要がある。
ここでは、そのインタフェースの型を指定するのだ。
一般的には、ここで IUnknown を使うことが多い。
IUnknown は、COM インタフェースの基底であり、
あらゆる COM インタフェースがこれを継承しているため、
結果的にあらゆる COM クラスで使えるからである。
しかしながら、ここでは実装しているのが明確なので、
IShellLinkW の ID を直接指定している。
最後の引数は、新しいインスタンスを操作するための、
IShellLinkW * ポインタを受け取る変数のアドレスだ。
C 言語では、引数を使って値を受け取ることが多いが、
その際、呼び出す側が変数のアドレスを渡すことで、
呼び出された関数が変数の値を変更できるようにする。
アドレス演算子 & を使って変数のアドレスを得て、
それを (void **) にキャストして渡すというのは、
COM ではよく見られる光景である。
* や & などは C 言語の一番ややこしい部分だ。
高級言語ではこういった要素をうまく隠し、
プログラマが意識する必要はない。
続いて、IPersistFile インタフェースにキャストする。
C 言語にもキャストの構文はあるが、
COM インタフェースでは、キャスト構文は禁じ手であり、
代わりに、QueryInterface メソッドを呼び出す。
QueryInterface は基底の IUnknown で定義されており、
あらゆるインタフェースが実装している。
C 言語では、クラスのメソッドを呼べないため、
COM マクロがここで役に立つ。
IUnknown の QueryInterface メソッドは、
IUnknown_QueryInterface というマクロ関数として、
第 1 引数にオブジェクトを渡すことで利用できる。
QueryInterface は、オブジェクトに対して、
引数に指定した IID のポインタを要求する。
オブジェクトがインタフェースを実装していれば、
適切なポインタが返却される仕組みだ。
ここでは、IID_IPersistFile を渡し、
IPersistFile インタフェースへのポインタを得る。
そして、IPersistFile の Load メソッドを呼び、
ファイルをオブジェクトに読み込み、
IShellLinkW の GetPath メソッドを呼び、
ショートカットの示すパスを文字列バッファに取得する。
このあたりは C# の時とあまり変わらない。
これで関数の仕事は終わりなのだが、
COM の後処理をする必要がある。
まず、COM インタフェースのポインタは、
自らの Release メソッドを呼んで解放する。
これも、IUnknown で定義されている。
そして、最後に CoUninitialize を呼んで終了となる。
C# では、10 行も使わないコードで済んだが、
C では、非常に長いコードになっていることが分かる。
まあ、C# ではインタフェース定義を書く必要があったので、
公平な比較ではないかもしれないが、
C# の定義はあらかじめ用意しておけば参照設定で済む。
C では、これ以上コードを省略することはできないのだ。