Partilhar via


Formato do arquivo de manifesto

O formato de arquivo para os arquivos de manifesto empresta o máximo possível de C++ e IDL. Como resultado, é bastante fácil pegar um arquivo de cabeçalho do SDK do C++ normal e modificá-lo para ser um arquivo de manifesto. O analisador dá suporte total a comentários de estilo C e C++ para ajudá-lo a organizar e documentar o arquivo.

Se você estiver tentando adicionar um arquivo de manifesto ou fazer alterações em um arquivo existente, a melhor maneira de fazer isso é apenas experimentar. Quando você emitir um comando !logexts.logi ou !logexts.loge no depurador, o Agente tentará analisar os arquivos de manifesto. Se encontrar um problema, ele produzirá uma mensagem de erro que pode indicar o erro.

Um arquivo de manifesto é composto pelos seguintes elementos básicos: rótulos de módulo, rótulos de categoria, declarações de função, definições de interface COM e definições de tipo. Outros tipos de elementos também existem, mas esses são os mais importantes.

Rótulos de módulo

Um rótulo de módulo simplesmente declara qual DLL exporta as funções que são declaradas posteriormente. Por exemplo, se o arquivo de manifesto for para registrar um grupo de funções de Comctl32.dll, você incluirá o seguinte rótulo de módulo antes de declarar os protótipos de função:

module COMCTL32.DLL:

Um rótulo de módulo deve aparecer antes de qualquer declaração de função em um arquivo de manifesto. Um arquivo de manifesto pode conter qualquer número de rótulos de módulo.

Rótulos de categoria

Semelhante a um rótulo de módulo, um rótulo de categoria identifica a qual "categoria" todas as funções subsequentes e/ou interfaces COM pertencem. Por exemplo, se você estiver criando um arquivo de manifesto Comctl32.dll, poderá usar o seguinte como rótulo de categoria:

category CommonControls:

Um arquivo de manifesto pode conter qualquer número de rótulos de categoria.

Declarações de função

Uma declaração de função é o que realmente solicita que o Agente registre algo. Ele é quase idêntico a um protótipo de função encontrado em um arquivo de cabeçalho C/C++. Há algumas adições notáveis ao formato, que podem ser melhor ilustradas pelo seguinte exemplo:

HANDLE [gle] FindFirstFileA(
       LPCSTR lpFileName,
       [out] LPWIN32_FIND_DATAA lpFindFileData);

A função FindFirstFileA usa dois parâmetros. O primeiro é lpFileName, que é um caminho completo (geralmente com curingas) definindo onde pesquisar um arquivo ou arquivos. O segundo é um ponteiro para uma estrutura WIN32_FIND_DATAA que será usada para conter os resultados da pesquisa. O HANDLE retornado é usado para chamadas futuras para FindNextFileA. Se FindFirstFileA retornar INVALID_HANDLE_VALUE, a chamada de função falhará e um código de erro poderá ser adquirido chamando a função GetLastError .

O tipo HANDLE é declarado da seguinte maneira:

value DWORD HANDLE
{
#define NULL                       0 [fail]
#define INVALID_HANDLE_VALUE      -1 [fail]
};

Se o valor retornado por essa função for 0 ou -1 (0xFFFFFFFF), o Agente assumirá que a função falhou, pois esses valores têm um modificador [fail] na declaração de valor. (Consulte a seção Tipos de Valor mais adiante nesta seção.) Como há um modificador [gle] logo antes do nome da função, o Agente reconhece que essa função usa GetLastError para retornar códigos de erro, portanto, captura o código de erro e o registra no arquivo de log.

O modificador [out] no parâmetro lpFindFileData informa ao Agente que a estrutura de dados é preenchida pela função e deve ser registrada quando a função retorna.

Definições de interface COM

Uma interface COM é basicamente um vetor de funções que podem ser chamadas pelo cliente de um objeto COM. O formato de manifesto é emprestado fortemente da Linguagem de Definição de Interface (IDL) usada em COM para definir interfaces.

Considere o seguinte exemplo:

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

Isso declara uma interface chamada IDispatch derivada de IUnknown. Ele contém quatro funções membro, que são declaradas em ordem específica dentro das chaves da interface. O agente interceptará e registrará essas funções membro substituindo os ponteiros de função na vtable da interface (o vetor binário real de ponteiros de função usados em tempo de execução) por seus próprios. Consulte a seção Tipos de COM_INTERFACE_PTR mais adiante nesta seção para obter mais detalhes sobre como o Agente captura interfaces conforme elas são entregues.

Definições de tipo

Definir tipos de dados é a parte mais importante (e mais tediosa) do desenvolvimento de arquivos de manifesto. A linguagem de manifesto permite definir rótulos legíveis por humanos para valores numéricos que são passados ou retornados de uma função.

Por exemplo, Winerror.h define um tipo chamado "WinError" que é uma lista de valores de erro retornados pela maioria das funções do Microsoft Win32 e seus rótulos legíveis por humanos correspondentes. Isso permite que Logger e LogViewer substituam códigos de erro não informativos por texto significativo.

Você também pode rotular bits individuais dentro de uma máscara de bits para permitir que Logger e LogViewer dividam uma máscara de bits DWORD em seus componentes.

Há 13 tipos básicos compatíveis com o manifesto. Elas são listadas na tabela a seguir.

Digite Comprimento Exemplo de exibição

Ponteiro

4 bytes

0x001AF320

VAZIO

0 bytes

BYTE

1 byte

0x32

WORD

2 bytes

0x0A23

DWORD

4 bytes

-234323

BOOL

1 byte

TRUE

LPSTR

Byte de comprimento mais qualquer número de caracteres

"Raposa marrom rápida"

LPWSTR

Byte de comprimento mais qualquer número de caracteres Unicode

"Pulou sobre o cachorro preguiçoso"

GUID

16 bytes

{0CF774D0-F077-11D1-B1BC-00C04F86C324}

COM_INTERFACE_PTR

4 bytes

0x0203404A

value

Dependente do tipo base

ERROR_TOO_MANY_OPEN_FILES

mask

Dependente do tipo base

WS_MAXIMIZED | WS_ALWAYSONTOP

struct

Dependente do tamanho dos tipos encapsulados

+ lpRect nLeft 34 nRight 54 nTop 100 nBottom 300

As definições de tipo em arquivos de manifesto funcionam como typedefs C/C++. Por exemplo, a seguinte instrução define PLONG como um ponteiro para um LONG:

typedef LONG *PLONG;

A maioria dos typedefs básicos já foi declarada em Main.h. Você só deve adicionar typedefs específicos ao seu componente. As definições de estrutura têm o mesmo formato que os tipos de struct C/C++.

Há quatro tipos especiais: valor, máscara, GUID e COM_INTERFACE_PTR.

Tipos de valor
Um valor é um tipo básico que é dividido em rótulos legíveis por humanos. A maioria da documentação da função refere-se apenas ao valor #define de uma constante específica usada em uma função. Por exemplo, a maioria dos programadores não sabe qual é o valor real para todos os códigos retornados por GetLastError, tornando inútil ver um valor numérico enigmático no LogViewer. O valor do manifesto supera isso permitindo declarações de valor como no exemplo a seguir:

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

Isso declara um novo tipo chamado "ChangeNotifyFlags" derivado de LONG. Se isso for usado como um parâmetro de função, os aliases legíveis por humanos serão exibidos em vez dos números brutos.

Tipos de máscara
Semelhante aos tipos de valor, um tipo de máscara é um tipo básico (geralmente um DWORD) que é dividido em rótulos legíveis por humanos para cada um dos bits que têm significado. Veja o exemplo seguinte:

mask DWORD DirectDrawOptSurfaceDescCapsFlags
{
#define DDOSDCAPS_OPTCOMPRESSED                 0x00000001
#define DDOSDCAPS_OPTREORDERED                  0x00000002
#define DDOSDCAPS_MONOLITHICMIPMAP              0x00000004
};

Isso declara um novo tipo derivado de DWORD que, se usado como um parâmetro de função, terá os valores individuais divididos para o usuário no LogViewer. Portanto, se o valor for 0x00000005, o LogViewer exibirá:

DDOSDCAPS_OPTCOMPRESSED | DDOSDCAPS_MONOLITHICMIPMAP

Tipos de GUID
GUIDs são identificadores globalmente exclusivos de 16 bytes que são usados extensivamente em COM. Eles são declarados de duas maneiras:

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

ou

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

O primeiro método é usado para declarar um IID (identificador de interface). Quando exibido pelo LogViewer, "IID_" é acrescentado ao início do nome de exibição. O segundo método é usado para declarar um CLSID (identificador de classe). O LogViewer acrescenta "CLSID_" ao início do nome de exibição.

Se um tipo GUID for um parâmetro para uma função, o LogViewer comparará o valor com todos os IIDs e CLSIDs declarados. Se uma correspondência for encontrada, ela exibirá o nome amigável do IID. Caso contrário, ele exibirá o valor de caractere hexadecimal de 32 caracteres hexadecimais na notação guid padrão.

Tipos de COM_INTERFACE_PTR
O tipo COM_INTERFACE_PTR é o tipo base de um ponteiro de interface COM. Ao declarar uma interface COM, na verdade, você está definindo um novo tipo derivado de COM_INTERFACE_PTR. Dessa forma, um ponteiro para esse tipo pode ser um parâmetro para uma função. Se um COM_INTERFACE_PTR tipo básico for declarado como um parâmetro OUT para uma função e houver um parâmetro separado que tenha um rótulo [iid], o Agente comparará o IID passado com todos os GUIDs declarados. Se houver uma correspondência e uma interface COM tiver o mesmo nome que o IID, o Agente conectará todas as funções nessa interface e as registrará em log.

Veja um exemplo:

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

Neste exemplo, riid tem um modificador [iid]. Isso indica ao Agente que o ponteiro retornado em ppv é um ponteiro de interface COM para a interface identificada por riid.

Também é possível declarar uma função da seguinte maneira:

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

Neste exemplo, LPDIRECTDRAWCLIPPER é definido como um ponteiro para a interface IDirectDrawClipper . Como o Agente pode identificar qual tipo de interface está sendo retornado no parâmetro lplpDDClipper , não há necessidade de um modificador [iid] em nenhum dos outros parâmetros.