Manifestdateiformat
Das Dateiformat für die Manifestdateien entlehnt so viel wie möglich von C++ und IDL. Daher ist es ziemlich einfach, eine normale C++-SDK-Headerdatei in eine Manifestdatei zu ändern. Der Parser unterstützt vollständig C- und C++-Kommentare, um Die Datei zu organisieren und zu dokumentieren.
Wenn Sie versuchen, eine Manifestdatei hinzuzufügen oder Änderungen an einer vorhandenen Datei vorzunehmen, besteht die beste Möglichkeit dazu darin, einfach zu experimentieren. Wenn Sie einen !logexts.logi - oder !logexts.loge-Befehl im Debugger ausgeben, versucht Logger, die Manifestdateien zu analysieren. Wenn ein Problem auftritt, wird eine Fehlermeldung erzeugt, die auf den Fehler hinweisen kann.
Eine Manifestdatei besteht aus den folgenden grundlegenden Elementen: Modulbezeichnungen, Kategoriebezeichnungen, Funktionsdeklarationen, COM-Schnittstellendefinitionen und Typdefinitionen. Es gibt auch andere Arten von Elementen, aber diese sind die wichtigsten.
Modulbeschriftungen
Eine Modulbezeichnung deklariert einfach, welche DLL die funktionen exportiert, die danach deklariert werden. Wenn Ihre Manifestdatei beispielsweise für die Protokollierung einer Gruppe von Funktionen aus Comctl32.dll vorgesehen ist, fügen Sie die folgende Modulbezeichnung ein, bevor Sie Funktionsprototypen deklarieren:
module COMCTL32.DLL:
Eine Modulbezeichnung muss vor funktionsdeklarationen in einer Manifestdatei angezeigt werden. Eine Manifestdatei kann eine beliebige Anzahl von Modulbezeichnungen enthalten.
Kategoriebezeichnungen
Ähnlich wie bei einer Modulbezeichnung gibt eine Kategoriebezeichnung an, zu welcher "Kategorie" alle nachfolgenden Funktionen und/oder COM-Schnittstellen gehören. Wenn Sie beispielsweise eine Comctl32.dll Manifestdatei erstellen, können Sie Folgendes als Kategoriebezeichnung verwenden:
category CommonControls:
Eine Manifestdatei kann eine beliebige Anzahl von Kategoriebezeichnungen enthalten.
Funktionsdeklarationen
Eine Funktionsdeklaration fordert die Protokollierung auf, etwas zu protokollieren. Es ist fast identisch mit einem Funktionsprototyp in einer C/C++-Headerdatei. Es gibt einige wichtige Ergänzungen zum Format, die am besten durch das folgende Beispiel veranschaulicht werden können:
HANDLE [gle] FindFirstFileA(
LPCSTR lpFileName,
[out] LPWIN32_FIND_DATAA lpFindFileData);
Die Funktion FindFirstFileA akzeptiert zwei Parameter. Die erste ist lpFileName. Dabei handelt es sich um einen vollständigen Pfad (in der Regel mit Wildcards), der definiert, wo nach einer Datei oder Dateien gesucht werden soll. Der zweite ist ein Zeiger auf eine WIN32_FIND_DATAA Struktur, die verwendet wird, um die Suchergebnisse zu enthalten. Das zurückgegebene HANDLE wird für zukünftige Aufrufe von FindNextFileA verwendet. Wenn FindFirstFileA INVALID_HANDLE_VALUE zurückgibt, ist der Funktionsaufruf fehlgeschlagen, und ein Fehlercode kann durch Aufrufen der GetLastError-Funktion abgerufen werden.
Der HANDLE-Typ wird wie folgt deklariert:
value DWORD HANDLE
{
#define NULL 0 [fail]
#define INVALID_HANDLE_VALUE -1 [fail]
};
Wenn der von dieser Funktion zurückgegebene Wert 0 oder -1 (0xFFFFFFFF) ist, geht Logger davon aus, dass die Funktion fehlgeschlagen ist, da diese Werte in der Wertdeklaration einen [fail]-Modifizierer aufweisen. (Weitere Informationen finden Sie im Abschnitt Werttypen weiter unten in diesem Abschnitt.) Da direkt vor dem Funktionsnamen ein [gle]-Modifizierer vorhanden ist, erkennt Logger, dass diese Funktion GetLastError verwendet, um Fehlercodes zurückzugeben. Daher wird der Fehlercode erfasst und in der Protokolldatei protokolliert.
Der [out]-Modifizierer für den lpFindFileData-Parameter informiert Logger darüber, dass die Datenstruktur von der Funktion ausgefüllt wird und protokolliert werden soll, wenn die Funktion zurückgibt.
COM-Schnittstellendefinitionen
Eine COM-Schnittstelle ist im Grunde ein Vektor von Funktionen, die vom Client eines COM-Objekts aufgerufen werden können. Das Manifestformat entspricht stark der IDL (Interface Definition Language), die in COM zum Definieren von Schnittstellen verwendet wird.
Betrachten Sie das folgende Beispiel:
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 );
};
Dadurch wird eine Schnittstelle namens IDispatch deklariert, die von IUnknown abgeleitet wird. Sie enthält vier Memberfunktionen, die in einer bestimmten Reihenfolge innerhalb der Klammern der Schnittstelle deklariert werden. Die Protokollierung fängt diese Memberfunktionen ab und protokolliert sie, indem die Funktionszeiger in der vtable der Schnittstelle (der tatsächliche binäre Vektor der zur Laufzeit verwendeten Funktionszeiger) durch eine eigene ersetzt werden. Im Abschnitt COM_INTERFACE_PTR Types weiter unten in diesem Abschnitt finden Sie weitere Informationen dazu, wie Die Protokollierung Schnittstellen während der Übergabe erfasst.
Typdefinitionen
Das Definieren von Datentypen ist der wichtigste (und mühsamste) Teil der Manifestdateientwicklung. Mit der Manifestsprache können Sie lesbare Bezeichnungen für numerische Werte definieren, die von einer Funktion übergeben oder zurückgegeben werden.
Winerror.h definiert beispielsweise einen Typ namens "WinError", bei dem es sich um eine Liste von Fehlerwerten handelt, die von den meisten Microsoft Win32-Funktionen zurückgegeben werden, und deren entsprechenden beschriftbaren Bezeichnungen. Dadurch können Logger und LogViewer uninformative Fehlercodes durch aussagekräftigen Text ersetzen.
Sie können auch einzelne Bits innerhalb einer Bitmaske bezeichnen, damit Logger und LogViewer eine DWORD-Bitmaske in ihre Komponenten aufteilen können.
Es gibt 13 grundlegende Typen, die vom Manifest unterstützt werden. Sie sind in der folgenden Tabelle aufgeführt.
Typ | Länge | Beispiel für die Anzeige |
---|---|---|
Zeiger |
4 Byte |
0x001AF320 |
LEERE |
0 Byte |
|
BYTE |
1 Byte |
0x32 |
WORD |
2 Bytes |
0x0A23 |
DWORD |
4 Byte |
-234323 |
BOOL |
1 Byte |
TRUE |
LPSTR |
Längenbyte plus beliebige Anzahl von Zeichen |
"Schneller Braunfuchs" |
LPWSTR |
Längenbyte plus beliebige Anzahl von Unicode-Zeichen |
"Über den faulen Hund gesprungen" |
GUID |
16 Bytes |
{0CF774D0-F077-11D1-B1BC-00C04F86C324} |
COM_INTERFACE_PTR |
4 Byte |
0x0203404A |
value |
Abhängig vom Basistyp |
ERROR_TOO_MANY_OPEN_FILES |
mask |
Abhängig vom Basistyp |
WS_MAXIMIZED | WS_ALWAYSONTOP |
struct |
Abhängig von der Größe von gekapselten Typen |
+ lpRect nLeft 34 nRight 54 nTop 100 nBottom 300 |
Typdefinitionen in Manifestdateien funktionieren wie C/C++-Typdefinitionen. Die folgende Anweisung definiert Z. B. PLONG als Zeiger auf einen LONG-Wert:
typedef LONG *PLONG;
Die meisten grundlegenden Typedefs wurden bereits in Main.h deklariert. Sie sollten nur Typdefinitionen hinzufügen müssen, die für Ihre Komponente spezifisch sind. Strukturdefinitionen haben das gleiche Format wie C/C++-Strukturtypen.
Es gibt vier spezielle Typen: Value, Mask, GUID und COM_INTERFACE_PTR.
Werttypen
Ein Wert ist ein grundlegender Typ, der in lesbare Bezeichnungen unterteilt wird. Die meisten Funktionsdokumentationen beziehen sich nur auf den #define Wert einer bestimmten Konstanten, die in einer Funktion verwendet wird. Beispielsweise wissen die meisten Programmierer nicht, was der tatsächliche Wert für alle von GetLastError zurückgegebenen Codes ist, sodass es nicht hilfreich ist, einen kryptischen numerischen Wert in LogViewer zu sehen. Der Manifestwert überwindet dies, indem Er Wertdeklarationen wie im folgenden Beispiel zulässt:
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
};
Dadurch wird ein neuer Typ namens "ChangeNotifyFlags" deklariert, der von LONG abgeleitet wird. Wenn dies als Funktionsparameter verwendet wird, werden die für Menschen lesbaren Aliase anstelle der rohen Zahlen angezeigt.
Maskierungstypen
Ähnlich wie bei Werttypen ist ein Maskentyp ein Basistyp (in der Regel ein DWORD), der in lesbare Bezeichnungen für jedes der Bits unterteilt wird, die eine Bedeutung haben. Betrachten Sie das folgende Beispiel:
mask DWORD DirectDrawOptSurfaceDescCapsFlags
{
#define DDOSDCAPS_OPTCOMPRESSED 0x00000001
#define DDOSDCAPS_OPTREORDERED 0x00000002
#define DDOSDCAPS_MONOLITHICMIPMAP 0x00000004
};
Dadurch wird ein neuer, von DWORD abgeleiteter Typ deklariert, der bei Verwendung als Funktionsparameter die einzelnen Werte für den Benutzer in LogViewer aufschlüsselt. Wenn der Wert also 0x00000005 ist, zeigt LogViewer Folgendes an:
DDOSDCAPS_OPTCOMPRESSED | DDOSDCAPS_MONOLITHICMIPMAP
GUID-Typen
GUIDs sind global eindeutige 16-Byte-Bezeichner, die in COM häufig verwendet werden. Sie werden auf zwei Arten deklariert:
struct __declspec(uuid("00020400-0000-0000-C000-000000000046")) IDispatch;
oder
class __declspec(uuid("11219420-1768-11D1-95BE-00609797EA4F")) ShellLinkObject;
Die erste Methode wird verwendet, um einen Schnittstellenbezeichner (Interface Identifier, IID) zu deklarieren. Wenn sie von LogViewer angezeigt wird, wird "IID_" am Anfang des Anzeigenamens angefügt. Die zweite Methode wird verwendet, um einen Klassenbezeichner (CLSID) zu deklarieren. LogViewer fügt "CLSID_" am Anfang des Anzeigenamens an.
Wenn ein GUID-Typ ein Parameter für eine Funktion ist, vergleicht LogViewer den Wert mit allen deklarierten IIDs und CLSIDs. Wenn eine Übereinstimmung gefunden wird, wird der Anzeigename IID angezeigt. Andernfalls wird der 32-Hexadezimalzeichenwert in der GUID-Standardnotation angezeigt.
COM_INTERFACE_PTR-Typen
Der COM_INTERFACE_PTR Typ ist der Basistyp eines COM-Schnittstellenzeigers. Wenn Sie eine COM-Schnittstelle deklarieren, definieren Sie tatsächlich einen neuen Typ, der von COM_INTERFACE_PTR abgeleitet wird. Daher kann ein Zeiger auf einen solchen Typ ein Parameter für eine Funktion sein. Wenn ein COM_INTERFACE_PTR Basistyp als OUT-Parameter für eine Funktion deklariert wird und ein separater Parameter mit einer [iid]-Bezeichnung vorhanden ist, vergleicht Logger die in IID übergebene mit allen deklarierten GUIDs. Wenn eine Übereinstimmung vorhanden ist und eine COM-Schnittstelle deklariert wurde, die denselben Namen wie die IID hat, bindet Logger alle Funktionen in dieser Schnittstelle ein und protokolliert sie.
Beispiel:
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
);
In diesem Beispiel verfügt riid über einen [iid]-Modifizierer. Dies gibt der Protokollierung an, dass der in ppv zurückgegebene Zeiger ein COM-Schnittstellenzeiger für die durch riid identifizierte Schnittstelle ist.
Es ist auch möglich, eine Funktion wie folgt zu deklarieren:
DDRESULT DirectDrawCreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter );
In diesem Beispiel wird LPDIRECTDRAWCLIPPER als Zeiger auf die IDirectDrawClipper-Schnittstelle definiert. Da logger erkennen kann, welcher Schnittstellentyp im lplpDDClipper-Parameter zurückgegeben wird, ist kein [iid]-Modifizierer für einen der anderen Parameter erforderlich.