Condividi tramite


Formato file manifest

Il formato di file per i file manifesto prende in prestito il maggior numero possibile da C++ e IDL. Di conseguenza, è abbastanza facile accettare un normale file di intestazione dell'SDK C++ e modificarlo in modo che sia un file manifesto. Il parser supporta completamente i commenti di stile C e C++ per organizzare e documentare il file.

Se si sta tentando di aggiungere un file manifesto o apportare modifiche a un file esistente, il modo migliore per farlo è solo sperimentare. Quando si esegue un comando !logexts.logi o !logexts.loge nel debugger, Logger tenterà di analizzare i file manifesto. Se si verifica un problema, genererà un messaggio di errore che potrebbe indicare l'errore.

Un file manifesto è costituito dagli elementi di base seguenti: etichette dei moduli, etichette di categoria, dichiarazioni di funzione, definizioni di interfaccia COM e definizioni dei tipi. Esistono anche altri tipi di elementi, ma questi sono i più importanti.

Etichette dei moduli

Un'etichetta di modulo dichiara semplicemente la DLL che esporta le funzioni dichiarate successivamente. Ad esempio, se il file manifesto è per registrare un gruppo di funzioni da Comctl32.dll, è necessario includere l'etichetta del modulo seguente prima di dichiarare i prototipi di funzione:

module COMCTL32.DLL:

Un'etichetta del modulo deve essere visualizzata prima di qualsiasi dichiarazione di funzione in un file manifesto. Un file manifesto può contenere un numero qualsiasi di etichette di modulo.

Etichette di categoria

Analogamente a un'etichetta di modulo, un'etichetta di categoria identifica a quali "categoria" appartengono tutte le funzioni successive e/o le interfacce COM. Ad esempio, se si crea un file manifesto Comctl32.dll, è possibile usare quanto segue come etichetta di categoria:

category CommonControls:

Un file manifesto può contenere un numero qualsiasi di etichette di categoria.

Dichiarazioni di funzione

Una dichiarazione di funzione è ciò che richiede effettivamente a Logger di registrare un elemento. È quasi identico a un prototipo di funzione trovato in un file di intestazione C/C++. Esistono alcune aggiunte rilevanti al formato, che possono essere illustrate meglio nell'esempio seguente:

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

La funzione FindFirstFileA accetta due parametri. Il primo è lpFileName, ovvero un percorso completo (in genere con caratteri jolly) che definisce dove cercare uno o più file. Il secondo è un puntatore a una struttura WIN32_FIND_DATAA che verrà usata per contenere i risultati della ricerca. L'handle restituito viene usato per le chiamate future a FindNextFileA. Se FindFirstFileA restituisce INVALID_HANDLE_VALUE, la chiamata di funzione non è riuscita e un codice di errore può essere acquistato chiamando la funzione GetLastError .

Il tipo HANDLE viene dichiarato come segue:

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

Se il valore restituito da questa funzione è 0 o -1 (0xFFFFFFFF), Logger presuppone che la funzione non sia riuscita perché tali valori hanno un modificatore [fail] nella dichiarazione di valore. Vedere la sezione Tipi di valore più avanti in questa sezione. Poiché è presente un modificatore [gle] immediatamente prima del nome della funzione, Logger riconosce che questa funzione usa GetLastError per restituire i codici di errore, quindi acquisisce il codice di errore e lo registra nel file di log.

Il modificatore [out] nel parametro lpFindFileData informa Logger che la struttura dei dati viene compilata dalla funzione e deve essere registrata al termine della funzione.

Definizioni di interfaccia COM

Un'interfaccia COM è fondamentalmente un vettore di funzioni che possono essere chiamate dal client di un oggetto COM. Il formato manifesto prende in prestito molto dal linguaggio IDL (Interface Definition Language) usato in COM per definire le interfacce.

Si consideri l'esempio seguente:

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

In questo modo viene dichiarata un'interfaccia denominata IDispatch derivata da IUnknown. Contiene quattro funzioni membri, che sono dichiarate in un ordine specifico all'interno delle parentesi graffe dell'interfaccia. Logger intercetta e registra queste funzioni membro sostituendo i puntatori di funzione nella vtable dell'interfaccia (il vettore binario effettivo dei puntatori di funzione usati in fase di esecuzione) con il proprio. Consultare la sezione sui tipi di COM_INTERFACE_PTR più avanti per ulteriori dettagli su come Logger acquisisce le interfacce man mano che vengono distribuite.

Definizioni dei tipi

La definizione dei tipi di dati è la parte più importante (e più noiosa) dello sviluppo di file manifesto. Il linguaggio manifesto consente di definire etichette leggibili per i valori numerici passati o restituiti da una funzione.

Ad esempio, Winerror.h definisce un tipo denominato "WinError" che è un elenco di valori di errore restituiti dalla maggior parte delle funzioni Di Microsoft Win32 e delle corrispondenti etichette leggibili. Ciò consente a Logger e LogViewer di sostituire i codici di errore non informativi con testo significativo.

È anche possibile etichettare singoli bit all'interno di una maschera di bit per consentire a Logger e LogViewer di suddividere una maschera di bit DWORD nei relativi componenti.

Esistono 13 tipi di base supportati dal manifesto. Sono elencati nella tabella seguente.

TIPO Durata Esempio di visualizzazione

Puntatore

4 byte

0x001AF320

VUOTO

0 byte

byte

1 byte

0x32

PAROLA

2 byte

0x0A23

DWORD

4 byte

-234323

BOOL

1 byte

TRUE

LPSTR

Byte di lunghezza più qualsiasi numero di caratteri

"Volpe bruna veloce"

LPWSTR

Byte di lunghezza più qualsiasi numero di caratteri Unicode

"Saltato sopra il cane pigro"

Identificatore Unico Globale (GUID)

16 byte

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

COM_INTERFACE_PTR

4 byte

0x0203404A

valore

Dipendente dal tipo di base

ERRORE_TROPPI_FILE_APERTI

maschera

Dipendente dal tipo di base

WS_MAXIMIZED | WS_ALWAYSONTOP

struttura

Dipendente dalle dimensioni dei tipi incapsulati

+ lpRect nLeft 34 nRight 54 nTop 100 nBottom 300

Le definizioni dei tipi nei file manifesto funzionano come typedef C/C++. Ad esempio, l'istruzione seguente definisce PLONG come puntatore a un LONG:

typedef LONG *PLONG;

La maggior parte dei typedef di base è già stata dichiarata in Main.h. Dovresti solo aggiungere typedef specifici al tuo componente. Le definizioni di struttura hanno lo stesso formato dei tipi di struct C/C++.

Esistono quattro tipi speciali: value, mask, GUID e COM_INTERFACE_PTR.

Tipi valore
Un valore è un tipo di base suddiviso in etichette leggibili. La maggior parte della documentazione della funzione si riferisce solo al valore #define di una determinata costante usata in una funzione. Ad esempio, la maggior parte dei programmatori non è a conoscenza del valore effettivo per tutti i codici restituiti da GetLastError, rendendo non utile vedere un valore numerico criptico in LogViewer. Il valore del manifesto supera questo problema consentendo dichiarazioni di valore come nell'esempio seguente:

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

Viene dichiarato un nuovo tipo denominato "ChangeNotifyFlags" derivato da LONG. Se viene usato come parametro di funzione, gli alias leggibili verranno visualizzati anziché i numeri non elaborati.

Tipi di maschera
Analogamente ai tipi valore, un tipo maschera è un tipo di base (in genere un DWORD) suddiviso in etichette leggibili per ognuno dei bit che hanno un significato specifico. Ad esempio:

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

In questo modo viene dichiarato un nuovo tipo derivato da DWORD che, se usato come parametro di funzione, avrà i singoli valori suddivisi per l'utente in LogViewer. Pertanto, se il valore è 0x00000005, LogViewer visualizzerà:

DDOSDCAPS_OPTCOMPRESSED | DDOSDCAPS_MONOLITHICMIPMAP

Tipi GUID
I GUID sono identificatori univoci globali a 16 byte usati ampiamente in COM. Vengono dichiarati in due modi:

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

o

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

Il primo metodo viene usato per dichiarare un identificatore di interfaccia (IID). Quando visualizzato da LogViewer, "IID_" viene aggiunto all'inizio del nome visualizzato. Il secondo metodo viene usato per dichiarare un identificatore di classe (CLSID). LogViewer aggiunge "CLSID_" all'inizio del nome visualizzato.

Se un tipo GUID è un parametro di una funzione, LogViewer confronta il valore con tutti gli ID E CLSID dichiarati. Se viene trovata una corrispondenza, verrà visualizzato il nome amichevole IID. In caso contrario, verrà visualizzato il valore di 32 caratteri esadecimali nella notazione GUID standard.

Tipi COM_INTERFACE_PTR
Il tipo COM_INTERFACE_PTR è il tipo di base di un puntatore a interfaccia COM. Quando si dichiara un'interfaccia COM, si definisce effettivamente un nuovo tipo derivato da COM_INTERFACE_PTR. Di conseguenza, un puntatore a tale tipo può essere un parametro di una funzione. Se un COM_INTERFACE_PTR tipo di base viene dichiarato come parametro OUT a una funzione ed è presente un parametro separato con un'etichetta [iid], Logger confronta il valore passato in IID con tutti i GUID dichiarati. Se è presente una corrispondenza e un'interfaccia COM è stata dichiarata con lo stesso nome dell'IID, Logger associa tutte le funzioni in tale interfaccia e le registra.

Ecco un esempio:

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 questo esempio riid ha un modificatore [iid]. Ciò indica a Logger che il puntatore restituito in ppv è un puntatore di interfaccia COM per l'interfaccia identificata da riid.

È anche possibile dichiarare una funzione come indicato di seguito:

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

In questo esempio LPDIRECTDRAWCLIPPER viene definito come puntatore all'interfaccia IDirectDrawClipper . Poiché Logger può identificare il tipo di interfaccia restituito nel parametro lplpDDClipper , non è necessario un modificatore [iid] in uno degli altri parametri.