Partager via


Format de fichier manifeste

Le format de fichier des fichiers manifeste emprunte autant que possible à C++ et IDL. Par conséquent, il est assez facile de prendre un fichier d’en-tête du SDK C++ normal et de le modifier en tant que fichier manifeste. L’analyseur prend entièrement en charge les commentaires de style C et C++ pour vous aider à organiser et documenter le fichier.

Si vous tentez d’ajouter un fichier manifeste ou d’apporter des modifications à un fichier existant, la meilleure façon de le faire est de simplement expérimenter. Lorsque vous émettez une commande !logexts.logi ou !logexts.loge dans le débogueur, Logger tente d’analyser les fichiers manifestes. S’il rencontre un problème, il génère un message d’erreur qui peut indiquer l’erreur.

Un fichier manifeste est constitué des éléments de base suivants : étiquettes de module, étiquettes de catégorie, déclarations de fonction, définitions d’interface COM et définitions de type. D’autres types d’éléments existent également, mais ceux-ci sont les plus importants.

Étiquettes de module

Une étiquette de module déclare simplement quelle DLL exporte les fonctions déclarées par la suite. Par exemple, si votre fichier manifeste sert à journaliser un groupe de fonctions à partir de Comctl32.dll, vous devez inclure l’étiquette de module suivante avant de déclarer des prototypes de fonction :

module COMCTL32.DLL:

Une étiquette de module doit apparaître avant toute déclaration de fonction dans un fichier manifeste. Un fichier manifeste peut contenir n’importe quel nombre d’étiquettes de module.

Étiquettes de catégorie

Comme pour une étiquette de module, une étiquette de catégorie identifie à quelle « catégorie » appartiennent toutes les fonctions et/ou interfaces COM suivantes. Par exemple, si vous créez un fichier manifeste Comctl32.dll, vous pouvez utiliser ce qui suit comme étiquette de catégorie :

category CommonControls:

Un fichier manifeste peut contenir n’importe quel nombre d’étiquettes de catégorie.

Déclarations de fonction

Une déclaration de fonction est ce qui invite en fait Logger à journaliser quelque chose. Il est presque identique à un prototype de fonction trouvé dans un fichier d’en-tête C/C++. Il existe quelques ajouts notables au format, qui peuvent être mieux illustrés par l’exemple suivant :

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

La fonction FindFirstFileA prend deux paramètres. Le premier est lpFileName, qui est un chemin d’accès complet (généralement avec des caractères génériques) qui définit où rechercher un ou plusieurs fichiers. La deuxième est un pointeur vers une structure WIN32_FIND_DATAA qui sera utilisée pour contenir les résultats de la recherche. Le HANDLE retourné est utilisé pour les appels futurs à FindNextFileA. Si FindFirstFileA retourne INVALID_HANDLE_VALUE, l’appel de fonction a échoué et un code d’erreur peut être obtenu en appelant la fonction GetLastError .

Le type HANDLE est déclaré comme suit :

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

Si la valeur retournée par cette fonction est 0 ou -1 (0xFFFFFFFF), Logger suppose que la fonction a échoué, car ces valeurs ont un modificateur [fail] dans la déclaration de valeur. (Voir la section Types de valeurs plus loin dans cette section.) Étant donné qu’il existe un modificateur [gle] juste avant le nom de la fonction, Logger reconnaît que cette fonction utilise GetLastError pour renvoyer les codes d’erreur, de sorte qu’elle capture le code d’erreur et le journalise dans le fichier journal.

Le modificateur [out] sur le paramètre lpFindFileData informe Logger que la structure de données est renseignée par la fonction et doit être journalisée lorsque la fonction retourne.

Définitions d’interface COM

Une interface COM est essentiellement un vecteur de fonctions qui peuvent être appelées par le client d’un objet COM. Le format de manifeste emprunte fortement au langage IDL (Interface Definition Language) utilisé dans COM pour définir des interfaces.

Prenons l’exemple suivant :

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

Cela déclare une interface appelée IDispatch qui est dérivée de IUnknown. Il contient quatre fonctions membres, qui sont déclarées dans un ordre spécifique dans les accolades de l’interface. L’enregistreur d’événements intercepte et consigne ces fonctions membres en remplaçant les pointeurs de fonction dans la table virtuelle de l’interface (vecteur binaire réel des pointeurs de fonction utilisés au moment de l’exécution) par son propre. Consultez la section Types de COM_INTERFACE_PTR plus loin dans cette section pour plus d’informations sur la façon dont Logger capture les interfaces au fur et à mesure qu’elles sont distribuées.

Définitions de type

La définition des types de données est la partie la plus importante (et la plus fastidieuse) du développement de fichiers manifestes. Le langage manifeste vous permet de définir des étiquettes lisibles par l’homme pour les valeurs numériques transmises ou retournées à partir d’une fonction.

Par exemple, Winerror.h définit un type appelé « WinError », qui est une liste de valeurs d’erreur retournées par la plupart des fonctions Microsoft Win32 et leurs étiquettes lisibles par l’homme correspondantes. Cela permet à Logger et LogViewer de remplacer les codes d’erreur non indicatifs par du texte explicite.

Vous pouvez également étiqueter des bits individuels dans un masque de bits pour permettre à Logger et LogViewer de briser un masque de bits DWORD dans ses composants.

Il existe 13 types de base pris en charge par le manifeste. Elles sont répertoriées dans le tableau suivant.

Type Longueur Exemple d’affichage

Pointeur

4 octets

0x001AF320

VIDE

0 octets

BYTE

1 octet

0x32

WORD

2 octets

0x0A23

DWORD

4 octets

-234323

BOOL

1 octet

TRUE

LPSTR

Octet de longueur plus nombre de caractères

« Renard brun rapide »

LPWSTR

Octet de longueur plus un nombre quelconque de caractères Unicode

« Sauté par-dessus le chien paresseux »

GUID

16 octets

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

COM_INTERFACE_PTR

4 octets

0x0203404A

value

Dépendant du type de base

ERROR_TOO_MANY_OPEN_FILES

masque

Dépendant du type de base

WS_MAXIMIZED | WS_ALWAYSONTOP

struct

Dépendant de la taille des types encapsulés

+ lpRect nLeft 34 nRight 54 nTop 100 nBottom 300

Les définitions de type dans les fichiers manifestes fonctionnent comme des typedefS C/C++. Par exemple, l’instruction suivante définit PLONG comme pointeur vers un LONG :

typedef LONG *PLONG;

La plupart des typedefs de base ont déjà été déclarés dans Main.h. Vous devez uniquement ajouter des typedefs spécifiques à votre composant. Les définitions de structure ont le même format que les types de struct C/C++.

Il existe quatre types spéciaux : value, mask, GUID et COM_INTERFACE_PTR.

Types de valeurs
Une valeur est un type de base qui est divisé en étiquettes lisibles par l’homme. La plupart de la documentation de fonction fait uniquement référence à la valeur #define d’une constante particulière utilisée dans une fonction. Par exemple, la plupart des programmeurs ignorent quelle est la valeur réelle pour tous les codes retournés par GetLastError, ce qui rend inutile l’affichage d’une valeur numérique chiffrée dans LogViewer. La valeur manifeste permet de surmonter ce problème en autorisant des déclarations de valeur comme dans l’exemple suivant :

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

Cela déclare un nouveau type appelé « ChangeNotifyFlags » dérivé de LONG. Si ce paramètre est utilisé comme paramètre de fonction, les alias lisibles par l’homme s’affichent à la place des nombres bruts.

Types de masque
Comme pour les types valeur, un type de masque est un type de base (généralement un DWORD) qui est divisé en étiquettes lisibles par l’homme pour chacun des bits qui ont une signification. Prenons l’exemple suivant :

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

Cela déclare un nouveau type dérivé de DWORD qui, s’il est utilisé comme paramètre de fonction, aura les valeurs individuelles décomposées pour l’utilisateur dans LogViewer. Ainsi, si la valeur est 0x00000005, LogViewer affiche :

DDOSDCAPS_OPTCOMPRESSED | DDOSDCAPS_MONOLITHICMIPMAP

GUID Types
Les GUID sont des identificateurs globaux uniques de 16 octets qui sont largement utilisés dans COM. Ils sont déclarés de deux façons :

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

or

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

La première méthode est utilisée pour déclarer un identificateur d’interface (IID). Lorsqu’il est affiché par LogViewer, « IID_ » est ajouté au début du nom d’affichage. La deuxième méthode est utilisée pour déclarer un identificateur de classe (CLSID). LogViewer ajoute « CLSID_ » au début du nom d’affichage.

Si un type GUID est un paramètre d’une fonction, LogViewer compare la valeur à tous les ID IID et CLSID déclarés. Si une correspondance est trouvée, le nom convivial de l’IID est affiché. Si ce n’est pas le cas, il affiche la valeur de 32 caractères hexadécimaux en notation GUID standard.

COM_INTERFACE_PTR Types
Le type COM_INTERFACE_PTR est le type de base d’un pointeur d’interface COM. Lorsque vous déclarez une interface COM, vous définissez en fait un nouveau type dérivé de COM_INTERFACE_PTR. Par conséquent, un pointeur vers un tel type peut être un paramètre vers une fonction. Si un COM_INTERFACE_PTR type de base est déclaré en tant que paramètre OUT pour une fonction et qu’il existe un paramètre distinct qui a une étiquette [iid], l’enregistreur d’événements compare le passé dans l’IID à tous les GUID déclarés. S’il existe une correspondance et qu’une interface COM portant le même nom que l’IID a été déclarée, l’enregistreur d’événements raccorde toutes les fonctions de cette interface et les enregistre.

Voici un exemple :

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

Dans cet exemple, riid a un modificateur [iid]. Cela indique à Logger que le pointeur retourné dans ppv est un pointeur d’interface COM pour l’interface identifiée par riid.

Il est également possible de déclarer une fonction comme suit :

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

Dans cet exemple, LPDIRECTDRAWCLIPPER est défini en tant que pointeur vers l’interface IDirectDrawClipper . Étant donné que Logger peut identifier le type d’interface retourné dans le paramètre lplpDDClipper , il n’est pas nécessaire d’utiliser un modificateur [iid] sur les autres paramètres.