Debugging Tools for Windows |
The file format for the manifest files borrows as much from C++ and IDL as possible. As a result, it is fairly easy to take a normal C++ SDK header file and modify it to be a manifest file. The parser fully supports C and C++ style comments to help you organize and document the file.
If you are attempting to add a manifest file or make alterations to an existing file, the best way to do it is to just experiment. When you issue a !logexts.logi or !logexts.loge command in the debugger, Logger will attempt to parse the manifest files. If it encounters a problem, it will produce an error message which might indicate the mistake.
A manifest file is made up of the following basic elements: module labels, category labels, function declarations, COM interface definitions, and type definitions. Other types of elements exist as well, but these are the most important.
A module label simply declares what DLL exports the functions that are declared thereafter. For example, if your manifest file is for logging a group of functions from COMCTL32.DLL, you would include the following module label before declaring any function prototypes:
A module label must appear before any function declarations in a manifest file. A manifest file can contain any number of module labels.
Similar to a module label, a category label identifies which "category" all subsequent functions and/or COM interfaces belong to. For example, if you are creating a COMCTL32.DLL manifest file, you can use the following as your category label:
A manifest file can contain any number of category labels.
An function declaration is what actually prompts Logger to log something. It is nearly identical to a function prototype found in a C/C++ header file. There are a few notable additions to the format, which can be best illustrated by the following example:
LPCSTR lpFileName,
[out] LPWIN32_FIND_DATAA lpFindFileData);
The function FindFirstFileA takes two parameters. The first is lpFileName, which is a full path (usually with wildcards) defining where to search for a file or files. The second is a pointer to a WIN32_FIND_DATAA structure that will be used to contain the search results. The returned HANDLE is used for future calls to FindNextFileA. If FindFirstFileA returns INVALID_HANDLE_VALUE, then the function call failed and an error code can be procured by calling the GetLastError function.
The HANDLE type is declared as follows:
{
#define NULL 0 [fail]
#define INVALID_HANDLE_VALUE -1 [fail]
};
If the value returned by this function is 0 or -1 (0xFFFFFFFF), Logger will assume that the function failed, because such values have a [fail] modifier in the value declaration. (See the Value Types section later in this section.) Since there is a [gle] modifier right before the function name, Logger recognizes that this function uses GetLastError to return error codes, so it captures the error code and logs it to the log file.
The [out] modifier on the lpFindFileData parameter informs Logger that the data structure is filled in by the function and should be logged when the function returns.
A COM interface is basically a vector of functions that can be called by a COM object's client. The manifest format borrows heavily from the Interface Definition Language (IDL) used in COM to define interfaces.
Consider the following example:
{
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 );
};
This declares an interface called IDispatch that is derived from IUnknown. It contains four member functions, which are declared in specific order within the interface's braces. Logger will intercept and log these member functions by replacing the function pointers in the interface's vtable (the actual binary vector of function pointers used at run time) with its own. See the COM_INTERFACE_PTR Types section later in this section for more details on how Logger captures interfaces as they are handed out.
Defining data types is the most important (and most tedious) part of manifest file development. The manifest language allows you to define human-readable labels for numeric values that are passed in or returned from a function.
For example, winerror.h defines a type called "WinError" that is a list of error values returned by most Microsoft Win32 functions and their corresponding human-readable labels. This allows Logger and LogViewer to replace uninformative error codes with meaningful text.
You can also label individual bits within a bit mask to allow Logger and LogViewer to break a DWORD bit mask into its components.
There are 13 basic types supported by the manifest. They are listed in the following table.
Type | Length | Display Example |
---|---|---|
Pointer | 4 bytes | 0x001AF320 |
VOID | 0 bytes | |
BYTE | 1 byte | 0x32 |
WORD | 2 bytes | 0x0A23 |
DWORD | 4 bytes | -234323 |
BOOL | 1 byte | TRUE |
LPSTR | Length byte plus any number of characters | "Quick brown fox" |
LPWSTR | Length byte plus any number of Unicode characters | "Jumped over the lazy dog" |
GUID | 16 bytes | {0CF774D0-F077-11D1-B1BC-00C04F86C324} |
COM_INTERFACE_PTR | 4 bytes | 0x0203404A |
value | Dependent on base type | ERROR_TOO_MANY_OPEN_FILES |
mask | Dependent on base type | WS_MAXIMIZED | WS_ALWAYSONTOP |
struct | Dependent on size of encapsulated types | + lpRect nLeft 34 nRight 54 nTop 100 nBottom 300 |
Type definitions in manifest files work like C/C++ typedefs. For example, the following statement defines PLONG as a pointer to a LONG:
Most basic typedefs have already been declared in main.h. You should only have to add typedefs that are specific to your component. Structure definitions have the same format as C/C++ struct types.
There are four special types: value, mask, GUID, and COM_INTERFACE_PTR.
{
#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
};
This declares a new type called "ChangeNotifyFlags" derived from LONG. If this is used as a function parameter, the human-readable aliases will be displayed instead of the raw numbers.
{
#define DDOSDCAPS_OPTCOMPRESSED 0x00000001
#define DDOSDCAPS_OPTREORDERED 0x00000002
#define DDOSDCAPS_MONOLITHICMIPMAP 0x00000004
};
This declares a new type derived from DWORD that, if used as a function parameter, will have the individual values broken out for the user in LogViewer. So, if the value is 0x00000005, LogViewer will display:
or
The first method is used to declare an interface identifier (IID). When displayed by LogViewer, "IID_" is appended to the beginning of the display name. The second method is used to declare a class identifier (CLSID). LogViewer appends "CLSID_" to the beginning of the display name.
If a GUID type is a parameter to a function, LogViewer will compare the value against all declared IIDs and CLSIDs. If a match is found, it will display the IID friendly name. If not, it will display the 32-hexadecimal-character value in standard GUID notation.
Here is an example:
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 this example, riid has an [iid] modifier. This indicates to Logger that the pointer returned in ppv is a COM interface pointer for the interface identified by riid.
It is also possible to declare a function as follows:
In this example, LPDIRECTDRAWCLIPPER is defined as a pointer to the IDirectDrawClipper interface. Since Logger can identify which interface type is being returned in the lplpDDClipper parameter, there is no need for an [iid] modifier on any of the other parameters.