Поделиться через


Формат файла манифеста

Формат файлов манифеста заимствует как можно больше из C++ и IDL. В результате довольно легко получить обычный файл заголовка пакета SDK для C++ и изменить его в качестве файла манифеста. Средство синтаксического анализа полностью поддерживает комментарии в стиле C и C++, помогающие упорядочивать и документировать файл.

Если вы пытаетесь добавить файл манифеста или внести изменения в существующий файл, лучший способ сделать это — просто поэкспериментировать. При выполнении команды !logexts.logi или !logexts.loge в отладчике средство ведения журнала попытается проанализировать файлы манифеста. Если возникает проблема, появится сообщение об ошибке, которое может указывать на ошибку.

Файл манифеста состоит из следующих основных элементов: меток модулей, меток категорий, объявлений функций, определений 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 принимает два параметра. Первый — lpFileName, который представляет собой полный путь (обычно с подстановочными знаками), определяющий, где искать файл или файлы. Второй — это указатель на структуру 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], средство ведения журнала распознает, что эта функция использует GetLastError для возврата кодов ошибок, поэтому записывает код ошибки и записывает его в файл журнала.

Модификатор [out] в параметре lpFindFileData сообщает Logger, что структура данных заполняется функцией и должна регистрироваться при возврате функции.

Определения com-интерфейсов

COM-интерфейс по сути является вектором функций, которые могут вызываться клиентом COM-объекта. Формат манифеста в значительной степени заимствуется из языка IDL, используемого в COM для определения интерфейсов.

Рассмотрим следующий пример.

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 );
};

При этом объявляется интерфейс IDispatch , производный от IUnknown. Он содержит четыре функции-члены, которые объявляются в определенном порядке в фигурных скобках интерфейса. Средство ведения журнала перехватывает и регистрирует эти функции-члены, заменяя указатели функций в vtable интерфейса (фактический двоичный вектор указателей функций, используемых во время выполнения) собственными. Дополнительные сведения о том, как средство ведения журнала захватывает интерфейсы при их раздаче, см. далее в разделе Типы COM_INTERFACE_PTR.

Определения типов

Определение типов данных является наиболее важной (и самой трудоемким) частью разработки файлов манифеста. Язык манифеста позволяет определять понятные метки для числовых значений, которые передаются или возвращаются из функции.

Например, Winerror.h определяет тип с именем "WinError", который представляет собой список значений ошибок, возвращаемых большинством функций Microsoft Win32 и соответствующими удобочитаемыми метками. Это позволяет logger и LogViewer заменять неинформативные коды ошибок понятным текстом.

Вы также можете пометить отдельные биты в битовой маске, чтобы разрешить Logger и LogViewer разбивать битовую маску DWORD на ее компоненты.

Манифест поддерживает 13 основных типов. Они перечислены в следующей таблице.

Тип Длина Пример отображения

Указатель

4 байта

0x001AF320

VOID

0 байт

BYTE

1 байт

0x32

WORD

2 байта

0x0A23

DWORD

4 байта

-234323

BOOL

1 байт

TRUE

LPSTR

Длина байтов плюс любое количество символов

"Быстрая коричневая лиса"

LPWSTR

Длина байтов и любое количество символов Юникода

"Прыгнул через ленивую собаку"

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++. Например, следующая инструкция определяет PLONG в качестве указателя на LONG:

typedef LONG *PLONG;

Большинство базовых типов уже объявлены в Main.h. Необходимо добавить только типообразующие файлы, относящиеся к компоненту. Определения структуры имеют тот же формат, что и типы структур C/C++.

Существует четыре специальных типа: value, mask, GUID и COM_INTERFACE_PTR.

Типы значений
Значение — это базовый тип, разбитый на удобочитаемые метки. Большинство документации по функциям относится только к #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
};

При этом объявляется новый тип с именем "ChangeNotifyFlags", производный от LONG. Если этот параметр используется в качестве параметра функции, вместо необработанных чисел будут отображаться доступные для чтения псевдонимы.

Типы маски
Как и типы значений, тип маски — это базовый тип (обычно 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
GUID — это 16-байтовые глобально уникальные идентификаторы, которые широко используются в COM. Они объявляются двумя способами:

struct __declspec(uuid("00020400-0000-0000-C000-000000000046")) IDispatch;

или

class __declspec(uuid("11219420-1768-11D1-95BE-00609797EA4F")) ShellLinkObject;

Первый метод используется для объявления идентификатора интерфейса (IID). При отображении LogViewer к началу отображаемого имени добавляется IID_. Второй метод используется для объявления идентификатора класса (CLSID). LogViewer добавляет "CLSID_" в начало отображаемого имени.

Если тип GUID является параметром функции, LogViewer сравнит значение со всеми объявленными идентификаторами IID и CLSID. Если совпадение найдено, отобразится понятное имя IID. В противном случае будет отображаться значение из 32 шестнадцатеричных символов в стандартной нотации GUID.

Типы COM_INTERFACE_PTR
Тип COM_INTERFACE_PTR является базовым типом указателя com-интерфейса. При объявлении COM-интерфейса фактически определяется новый тип, производный от COM_INTERFACE_PTR. Таким образом, указатель на такой тип может быть параметром функции. Если COM_INTERFACE_PTR базовый тип объявляется функцией как параметр OUT и имеется отдельный параметр с меткой [iid], средство ведения журнала сравнит переданный в IID параметр со всеми объявленными GUID. Если есть совпадение и был объявлен COM-интерфейс с тем же именем, что и IID, средство ведения журнала будет перехватывая все функции в этом интерфейсе и регистрирующие их.

Вот пример:

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 , является указателем COM-интерфейса для интерфейса, определяемого с помощью riid.

Функцию также можно объявить следующим образом:

DDRESULT DirectDrawCreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter );

В этом примере LPDIRECTDRAWCLIPPER определяется как указатель на интерфейс IDirectDrawClipper . Так как средство ведения журнала может определить, какой тип интерфейса возвращается в параметре lplpDDClipper , нет необходимости в модификаторе [iid] для любого из других параметров.