マニフェスト ファイルの形式
マニフェスト ファイルのファイル形式は、C++ と IDL から可能な限り多くを借用します。 その結果、通常の C++ SDK ヘッダー ファイルを取得し、マニフェスト ファイルに変更することは非常に簡単です。 パーサーは、ファイルの整理と文書化に役立つ C および C++ スタイルのコメントを完全にサポートしています。
マニフェスト ファイルを追加したり、既存のファイルに変更を加えたりする場合は、単に実験することをお勧めします。 デバッガーで !logexts.logi または !logexts.loge コマンドを発行すると、Logger はマニフェスト ファイルの解析を試みます。 問題が発生した場合は、誤りを示すエラー メッセージが生成されます。
マニフェスト ファイルは、モジュール ラベル、カテゴリ ラベル、関数宣言、COM インターフェイス定義、および型定義という基本的な要素で構成されます。 他の種類の要素も存在しますが、これらは最も重要です。
モジュール ラベル
モジュール ラベルは、その後に宣言された関数を DLL がエクスポートする内容を宣言するだけです。 たとえば、マニフェスト ファイルがComctl32.dllから関数のグループをログに記録する場合は、関数プロトタイプを宣言する前に、次のモジュール ラベルを含めます。
module COMCTL32.DLL:
モジュール ラベルは、マニフェスト ファイル内の関数宣言の前に表示する必要があります。 マニフェスト ファイルには、任意の数のモジュール ラベルを含めることができます。
カテゴリ ラベル
モジュール ラベルと同様に、カテゴリ ラベルは、後続のすべての関数や COM インターフェイスが属する "カテゴリ" を識別します。 たとえば、Comctl32.dllマニフェスト ファイルを作成する場合は、カテゴリ ラベルとして次を使用できます。
category CommonControls:
マニフェスト ファイルには、任意の数のカテゴリ ラベルを含めることができます。
関数の宣言
関数宣言は、ロガーに何かをログに記録するよう実際に求めるものです。 これは、C/C++ ヘッダー ファイルにある関数プロトタイプとほぼ同じです。 この形式にはいくつかの重要な追加事項があります。これは、次の例で最もよく示すことができます。
HANDLE [gle] FindFirstFileA(
LPCSTR lpFileName,
[out] LPWIN32_FIND_DATAA lpFindFileData);
関数 FindFirstFileA は2つのパラメーターを受け取ります。 1 つ目は lpFileName で、ファイルまたはファイルを検索する場所を定義する完全なパス (通常はワイルドカード)。 2 つ目は、検索結果を格納するために使用されるWIN32_FIND_DATAA構造体へのポインターです。 返された HANDLE は FindNextFileA への今後の呼び出しに使用されます。 FindFirstFileA がINVALID_HANDLE_VALUEを返した場合、関数の呼び出しは失敗し GetLastError 関数を呼び出すことによってエラー コードを取得できます。
HANDLE型は次のように宣言されます。
value DWORD HANDLE
{
#define NULL 0 [fail]
#define INVALID_HANDLE_VALUE -1 [fail]
};
この関数によって返される値が 0 または -1 (0xFFFFFFFF) の場合、ロガーは関数が失敗したと見なします。このような値には値宣言に [fail] 修飾子があるためです。 (このセクションの後の「値の型」セクションを参照してください)。関数名の直前に [gle] 修飾子があるため、Logger は、この関数が GetLastError を使用してエラー コードを返すので、エラー コードをキャプチャしてログ ファイルに記録することを認識します。
lpFindFileData パラメーターの [out] 修飾子は、データ構造が関数によって入力され、関数が戻ったときにログに記録される必要があることをロガーに通知します。
COM インターフェイス定義
COM インターフェイスは、基本的に COM オブジェクトのクライアントから呼び出すことができる関数のベクトルです。 マニフェスト形式は、COM でインターフェイスを定義するために使用されるインターフェイス定義言語 (IDL) から大きく借用されます。
次の例を考えてみましょう。
interface IDispatch : IUnknown
{
HRESULT GetTypeInfoCount( UINT pctinfo );
HRESULT GetTypeInfo(
UINT iTInfo,
LCID lcid,
LPVOID ppTInfo );
HRESULT GetIDsOfNames(
REFIID riid,
LPOLECHAR* rgszNames,
UINT cNames,
LCID lcid,
[out] DISPID* rgDispId );
HRESULT Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pVarResult,
EXCEPINFO* pExcepInfo,
UINT* puArgErr );
};
これにより IUnknown から派生した IDispatch というインターフェイスが宣言されます。 これには、インターフェイスの中かっこ内で特定の順序で宣言される 4 つのメンバー関数が含まれています。 ロガーは、インターフェイスの vtable 内の関数ポインター (実行時に使用される関数ポインターの実際のバイナリ ベクター) を独自の関数ポインターに置き換えることで、これらのメンバー関数をインターセプトしてログに記録します。 ロガーが渡されるインターフェイスのキャプチャ方法の詳細については、このセクションで後述する「COM_INTERFACE_PTR型」セクションを参照してください。
型定義
データ型の定義は、マニフェスト ファイル開発の最も重要な (そして最も面倒な) 部分です。 マニフェスト言語を使用すると、関数に渡されるか、関数から返される数値の人間が判読できるラベルを定義できます。
たとえば、Winerror.h は、ほとんどの Microsoft Win32 関数によって返されるエラー値とそれに対応する人間が判読できるラベルの一覧である "WinError" という型を定義します。 これにより、Logger と LogViewer は、フォーム化されていないエラー コードを意味のあるテキストに置き換えることができます。
また、ビット マスク内の個々のビットにラベルを付けて、Logger と LogViewer が DWORD ビット マスクをそのコンポーネントに分割できるようにすることもできます。
マニフェストでは、13 種類の基本型がサポートされています。 これらを次の表に示します。
Type | Length | 表示例 |
---|---|---|
ポインター |
4 バイト |
0x001AF320 |
VOID |
0 バイト |
|
バイト |
1 バイト |
0x32 |
WORD |
2 バイト |
0x0A23 |
DWORD |
4 バイト |
-234323 |
BOOL |
1 バイト |
TRUE |
LPSTR |
長さバイトと任意の数の文字 |
「クイック・ブラウン・フォックス」 |
LPWSTR |
長さのバイトに任意の数の Unicode 文字を加えたもの |
「怠け犬を飛び越えた」 |
GUID |
16 バイト |
{0CF774D0-F077-11D1-B1BC-00C04F86C324} |
COM_INTERFACE_PTR |
4 バイト |
0x0203404A |
値 |
基本型に依存 |
ERROR_TOO_MANY_OPEN_FILES |
マスク* |
基本型に依存 |
WS_MAXIMIZED |WS_ALWAYSONTOP |
struct |
カプセル化された型のサイズに依存 |
+ lpRect nLeft 34 nRight 54 nTop 100 nBottom 300 |
マニフェスト ファイル内の型定義は、C/C++ typedef と同様に機能します。 たとえば、次のステートメントは、LONG へのポインターとして PLONG を定義します。
typedef LONG *PLONG;
ほとんどの基本的な typedef は Main.h で既に宣言されています。 コンポーネントに固有の typedef のみを追加する必要があります。 構造体定義の形式は、C/C++ 構造体型と同じです。
特殊な型には、値、マスク、GUID、COM_INTERFACE_PTRの 4 種類があります。
値のタイプ
値は、人間が判読できるラベルに分割される基本型です。 ほとんどの関数ドキュメントは、関数で使用される特定の 定数の #define 値のみを参照します。 たとえば、ほとんどのプログラマは GetLastError によって返されるすべてのコードの実際の値が何であるかを認識していないので、LogViewer で不可解な数値を見るのは役に立ちません。 マニフェスト値は、次の例のように値宣言を許可することでこれを克服します。
value LONG ChangeNotifyFlags
{
#define SHCNF_IDLIST 0x0000 // LPITEMIDLIST
#define SHCNF_PATHA 0x0001 // path name
#define SHCNF_PRINTERA 0x0002 // printer friendly name
#define SHCNF_DWORD 0x0003 // DWORD
#define SHCNF_PATHW 0x0005 // path name
#define SHCNF_PRINTERW 0x0006 // printer friendly name
};
これにより、LONG から派生した "ChangeNotifyFlags" という新しい型が宣言されます。 これを関数パラメーターとして使用すると、人間が判読できるエイリアスが生の数値の代わりに表示されます。
マスクの種類
値型と同様に、マスク型は基本型 (通常は DWORD) であり、意味を持つ各ビットについて人間が判読できるラベルに分割されます。 次に例を示します。
mask DWORD DirectDrawOptSurfaceDescCapsFlags
{
#define DDOSDCAPS_OPTCOMPRESSED 0x00000001
#define DDOSDCAPS_OPTREORDERED 0x00000002
#define DDOSDCAPS_MONOLITHICMIPMAP 0x00000004
};
これにより、DWORD から派生した新しい型が宣言されます。関数パラメーターとして使用すると、LogViewer のユーザーに対して個別の値が分割されます。 そのため、値が0x00000005の場合、LogViewer は次の内容を表示します。
DDOSDCAPS_OPTCOMPRESSED | DDOSDCAPS_MONOLITHICMIPMAP
GUID Types
GUID は、COM で広く使用される 16 バイトのグローバル一意識別子です。 これらは、次の 2 つの方法で宣言されます。
struct __declspec(uuid("00020400-0000-0000-C000-000000000046")) IDispatch;
または
class __declspec(uuid("11219420-1768-11D1-95BE-00609797EA4F")) ShellLinkObject;
最初のメソッドは、インターフェイス識別子 (IID) を宣言するために使用されます。 LogViewer で表示すると、表示名の先頭に "IID_" が追加されます。 2 番目のメソッドは、クラス識別子 (CLSID) を宣言するために使用されます。 LogViewer は、表示名の先頭に "CLSID_" を追加します。
GUID 型が関数のパラメーターである場合、LogViewer は、宣言されているすべての IID と CLSID と値を比較します。 一致が見つかった場合は、IID フレンドリ名が表示されます。 そうでない場合は、標準の GUID 表記で 32 桁の 16 進文字の値が表示されます。
COM_INTERFACE_PTR 型
COM_INTERFACE_PTR型は、COM インターフェイス ポインターの基本型です。 COM インターフェイスを宣言すると、実際には、COM_INTERFACE_PTRから派生した新しい型が定義されます。 そのため、このような型へのポインターは、関数へのパラメーターにすることができます。 COM_INTERFACE_PTR基本型が関数の OUT パラメーターとして宣言されていて、[iid] ラベルを持つ別のパラメーターがある場合、Logger は IID で渡されたパラメーターを、宣言されたすべての GUID と比較します。 一致するものがあり、IID と同じ名前の COM インターフェイスが宣言されている場合、Logger はそのインターフェイス内のすべての関数をフックしてログに記録します。
例を次に示します。
STDAPI CoCreateInstance(
REFCLSID rclsid, //Class identifier (CLSID) of the object
LPUNKNOWN pUnkOuter, //Pointer to controlling IUnknown
CLSCTX dwClsContext, //Context for running executable code
[iid] REFIID riid, //Reference to the identifier of the interface
[out] COM_INTERFACE_PTR * ppv
//Address of output variable that receives
//the interface pointer requested in riid
);
この例では、 riid には [iid] 修飾子があります。 これは ppv で返されるポインターが riid で識別されるインターフェイスの COM インターフェイス ポインターであることをロガーに示します。
次のように関数を宣言することもできます。
DDRESULT DirectDrawCreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter );
この例では、LPDIRECTDRAWCLIPPER は IDirectDrawClipper インターフェイスへのポインターとして定義されています。 ロガーは lplpDDClipper パラメーターで返されるインターフェイス型を識別できるため、他のどのパラメーターにも [iid] 修飾子は必要ありません。