Cours sur COM [HRESULT, GUID, REGISTRY]

Ce forum est dédié à l'apprentissage de com comme par exemple directX , mediafoundation , les bases de com ....

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Cours sur COM [HRESULT, GUID, REGISTRY]

Post by Hydraxx »

Salut, :D

aujourd’hui on attaque un chapitre vraiment très important de COM. On ne crée pas encore d’objets COM au sens “factory + CoCreateInstance”, ça viendra juste après. Ici, on va plutôt poser les fondations indispensables, celles qu’il faut comprendre avant d’aller plus loin, sinon COM ressemble vite à une suite de fonctions obscures avec des GUID partout.

Le but de ce cours est donc de comprendre quatre briques centrales :
  • HRESULT, c’est-à-dire le système d’erreur COM
  • GUID, c’est-à-dire l’identité binaire globale des interfaces et des classes
  • le Registry COM, c’est-à-dire l’endroit où Windows localise les composants
  • la COM Library, c’est-à-dire le runtime de base qui initialise et aide tout ce modèle
Autrement dit, ici on ne voit pas encore “comment construire un objet COM”, mais on comprend déjà comment COM pense, comment il identifie les composants, comment il signale les erreurs, et comment Windows sait où aller chercher un composant donné.

1) HRESULT : le système d’erreur COM

Avant tout, il faut comprendre une chose simple : COM ne repose pas sur un modèle d’erreur binaire du style “ça marche / ça ne marche pas”. COM repose sur un système beaucoup plus riche, parce qu’un composant binaire peut échouer de plein de façons différentes, et parfois réussir avec une nuance importante.

C’est exactement pour ça que COM utilise HRESULT.

1.1 Pourquoi HRESULT existe

En C ou C++ très simple, on voit parfois :

Code: Select all

bool success;
ou alors :

Code: Select all

int result;
Le problème, c’est qu’un booléen ne dit presque rien :
  • pas d’origine
  • pas de catégorie
  • pas de code précis
  • pas de nuance entre plusieurs formes de succès
Or dans COM, un composant peut être :
  • local ou distant
  • in-process ou out-of-process
  • lié à des APIs Win32
  • lié à RPC
  • lié à OLE
  • lié à l’interface elle-même
Il faut donc un modèle d’erreur plus riche, plus extensible, et surtout stable au niveau binaire.

La réponse est :

Code: Select all

HRESULT
1.2 Structure interne d’un HRESULT

C’est un point très important. Un HRESULT est un entier 32 bits, mais il n’est pas “juste un nombre”. Il encode plusieurs informations.

Vision mentale simplifiée :

Code: Select all

| Severity | Facility | Code |
Version plus proche du vrai découpage NT/COM :
  • bit de sévérité
  • bits de facility
  • code d’erreur ou de succès
L’idée générale à retenir :
  • Severity indique succès ou échec
  • Facility indique d’où vient le code
  • Code indique la valeur concrète
Exemple classique :

Code: Select all

0x80070005
On le lit souvent mentalement ainsi :
  • 0x8... → bit d’erreur positionné, donc échec
  • facility 7 → origine Win32
  • code 5 → Access Denied
Autrement dit :

Code: Select all

HRESULT = erreur Win32 Access Denied emballée au format COM
C’est un exemple excellent, parce qu’il montre que COM ne remplace pas tout : il sait aussi transporter ou encapsuler des erreurs provenant d’autres sous-systèmes.

1.3 Codes HRESULT importants à connaître

Quelques constantes très fréquentes :

Code: Select all

S_OK
S_FALSE
E_FAIL
E_OUTOFMEMORY
E_POINTER
E_INVALIDARG
E_NOINTERFACE
E_NOTIMPL
E_ACCESSDENIED
CLASS_E_CLASSNOTAVAILABLE
REGDB_E_CLASSNOTREG
CO_E_NOTINITIALIZED
RPC_E_CHANGED_MODE
Il faut déjà bien comprendre ce que signifient les plus importantes :
  • S_OK → succès standard
  • S_FALSE → succès, mais pas “plein succès” au sens naïf
  • E_NOINTERFACE → interface demandée absente
  • E_NOTIMPL → méthode non implémentée
  • E_OUTOFMEMORY → manque mémoire
  • REGDB_E_CLASSNOTREG → classe COM non enregistrée
  • CO_E_NOTINITIALIZED → COM non initialisé sur le thread
  • RPC_E_CHANGED_MODE → problème de mode d’initialisation COM du thread
1.4 Piège critique : S_FALSE n’est pas un échec

C’est l’un des pièges les plus classiques du débutant :

Code: Select all

S_FALSE = succès
Il faut vraiment l’ancrer mentalement.

Si tu fais :

Code: Select all

if (hr == S_OK)
tu risques d’interpréter à tort certains succès comme des échecs.

La bonne approche est :

Code: Select all

if (SUCCEEDED(hr))
et pour les échecs :

Code: Select all

if (FAILED(hr))
Macros fondamentales :

Code: Select all

SUCCEEDED(hr)
FAILED(hr)
Règle importante :
  • ne jamais réduire COM à “S_OK ou erreur”
  • il peut exister plusieurs formes de succès
  • il peut exister beaucoup de codes d’erreur différents
1.5 HRESULT n’est pas un booléen déguisé

C’est une idée très importante.

Une fonction COM peut retourner :
  • plusieurs succès
  • plusieurs erreurs
  • des erreurs de facilities différentes
  • des codes propres au composant
Donc :

Code: Select all

COM ≠ booléen
Il faut penser HRESULT comme un code de retour riche, pas comme un simple drapeau.

1.6 Facilities : comprendre l’origine d’une erreur

Les facilities servent à savoir d’où vient l’erreur ou la famille logique de résultat.

Quelques facilities fréquentes à connaitre de nom :
  • FACILITY_NULL
  • FACILITY_RPC
  • FACILITY_DISPATCH
  • FACILITY_STORAGE
  • FACILITY_ITF
  • FACILITY_WIN32
  • FACILITY_SECURITY
FACILITY_ITF est particulièrement importante pour les composants COM qui définissent leurs propres erreurs d’interface.

Autrement dit :
  • si l’erreur vient du framework COM ou d’un sous-système connu, la facility l’indique
  • si le composant veut définir ses propres erreurs, il utilise souvent FACILITY_ITF
1.7 Définir ses propres HRESULT

Quand un composant COM veut définir ses propres codes, on utilise généralement :

Code: Select all

MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 100)
Règle importante :
  • pour ses propres erreurs COM d’interface, on utilise en général FACILITY_ITF
  • cela évite de brouiller l’origine avec des facilities système existantes
Il faut aussi garder en tête qu’un composant bien conçu doit documenter ses HRESULT propres.

1.8 Un composant COM peut évoluer, donc ses erreurs aussi

C’est un point très important côté client.

Un composant peut évoluer et ajouter :
  • de nouveaux codes d’erreur
  • de nouveaux cas de succès
  • de nouveaux scénarios réseau ou de sécurité
Donc un client sérieux doit :
  • gérer les HRESULT connus
  • mais aussi rester robuste face à des HRESULT inconnus
Un bon client COM ne doit pas être écrit comme s’il n’existait que S_OK et E_FAIL.

1.9 HRESULT et erreurs réseau / distribuées

COM peut être local, mais aussi out-of-process, et historiquement distribué via DCOM.

Conséquence :
  • le système d’erreur doit pouvoir transporter des erreurs liées à RPC
  • des erreurs de communication
  • des erreurs de marshaling
  • des erreurs de sécurité distribuée
C’est aussi pour ça que HRESULT est bien plus riche qu’un simple int arbitraire.

2) GUID : l’identité globale des composants COM

Deuxième fondation absolument centrale : les GUID.

Si HRESULT répond à la question :

Code: Select all

“qu’est-ce qu’il s’est passé ?”
le GUID répond à la question :

Code: Select all

“de quoi parle-t-on exactement ?”
2.1 Définition

GUID signifie Globally Unique Identifier.

C’est un identifiant 128 bits.

Représentation classique :

Code: Select all

XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Exemple :

Code: Select all

12345678-1234-1234-ABCD-1234567890AB
Le point fondamental à retenir :

Code: Select all

GUID = identité binaire globale
2.2 Pourquoi COM utilise des GUID

Le problème à résoudre est simple : éviter les collisions.

Si deux développeurs créent chacun une interface appelée :

Code: Select all

IMyInterface
le nom seul ne suffit pas au niveau binaire mondial.

COM veut une identité :
  • unique
  • stable
  • indépendante des noms symboliques
  • indépendante des collisions de chaînes
La solution :

Code: Select all

GUID
Ainsi, une interface ou une classe COM n’est pas identifiée de manière fiable par son nom texte, mais par un identifiant 128 bits unique.

2.3 GUID en COM : CLSID, IID, CATID

Dans le monde COM, plusieurs GUID spécialisés existent.

Les plus importants :
  • CLSID → identifie une classe COM
  • IID → identifie une interface COM
  • CATID → identifie une catégorie de composants
Résumé simple :
  • CLSID = quel composant concret
  • IID = quelle interface
  • CATID = quelle famille / catégorie logique
C’est une distinction très importante.

2.4 Génération d’un GUID

API importante :

Code: Select all

CoCreateGuid
Exemple logique :

Code: Select all

GUID g;
HRESULT hr = CoCreateGuid(&g);
On peut aussi utiliser des outils comme :
  • guidgen.exe
  • Visual Studio
  • certains assistants de génération de GUID
Règle pratique :
  • on ne “fabrique” pas un GUID à la main
  • on le génère avec les outils ou APIs prévus
2.5 Structure binaire GUID

Un GUID n’est pas juste une chaîne texte. C’est d’abord une structure mémoire.

Structure standard :

Code: Select all

typedef struct _GUID {
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[8];
} GUID;
Il faut retenir :
  • la représentation texte est une vue humaine
  • la structure GUID est la forme binaire réelle
C’est important quand on fait du debug, du reverse, du parsing mémoire, ou de l’interop plus bas niveau.

2.6 Déclaration de GUID dans le code

Pour déclarer un GUID dans le code, on utilise souvent :

Code: Select all

DEFINE_GUID(...)
On rencontre aussi le macro :

Code: Select all

INITGUID
Le point à retenir ici, c’est qu’en C/C++ COM classique, les GUID d’interface et de classe sont souvent définis via des macros standardisées pour garantir une représentation correcte et unique.

2.7 Comparer des GUID

API / macro importante :

Code: Select all

IsEqualGUID
Il faut éviter les comparaisons “bricolées” ou les comparaisons texte inutiles si on a déjà des GUID binaires.

Exemple logique :

Code: Select all

if (IsEqualGUID(iid, IID_IMonInterface))
{
    // interface reconnue
}
2.8 GUID passés par référence

En COM, on passe souvent les GUID par référence constante.

Exemples de typedefs importants :

Code: Select all

typedef const GUID& REFGUID;
typedef const IID&  REFIID;
typedef const CLSID& REFCLSID;
Pourquoi :
  • éviter des copies inutiles
  • garder une convention d’appel stable
  • améliorer l’efficacité et la clarté des signatures
Il faut reconnaitre visuellement ces types dans les prototypes COM.

2.9 Conversion GUID ↔ chaîne

APIs très utiles :

Code: Select all

StringFromGUID2
CLSIDFromString
IIDFromString
StringFromCLSID
StringFromIID
Utilité :
  • affichage lisible pour debug
  • lecture depuis configuration ou registry
  • outillage COM
  • interop avec chaînes
Exemple mental :
  • GUID → string pour affichage ou registry
  • string → GUID pour résolution ou parsing
3) Le Registry COM : où Windows trouve les composants

Une fois qu’on a compris HRESULT et GUID, on peut comprendre un autre point crucial :

Code: Select all

comment Windows sait où se trouve un composant COM ?
La réponse classique : le Registry.

3.1 Pourquoi COM utilise le Registry

COM est dynamique.

Autrement dit :
  • les composants peuvent être installés ou désinstallés
  • ils peuvent être remplacés
  • le client ne doit pas coder en dur l’adresse d’un binaire
  • le runtime doit pouvoir retrouver dynamiquement le bon serveur COM
Le Registry sert donc de base de localisation et de description.

Il relie :
  • un CLSID
  • à des informations techniques sur le composant
  • comme son chemin DLL ou EXE
  • son mode de threading
  • ses noms lisibles éventuels
3.2 Vue générale de la structure COM dans le Registry

La vue logique classique est :

Code: Select all

HKEY_CLASSES_ROOT
└── CLSID
    └── {GUID}
        ├── InprocServer32
        ├── LocalServer32
        ├── ProgID
        ├── VersionIndependentProgID
        └── autres sous-clés
Il faut déjà retenir plusieurs idées :
  • la clé CLSID est centrale
  • chaque CLSID a sa sous-clé
  • les sous-clés décrivent comment activer le composant
3.3 HKEY_CLASSES_ROOT n’est pas une ruche “simple”

C’est un point important et souvent mal compris.

HKEY_CLASSES_ROOT, ou HKCR, est une vue fusionnée, historiquement liée notamment à :
  • HKLM\Software\Classes
  • HKCU\Software\Classes
Donc :
  • HKCR est pratique à lire
  • mais en interne il faut garder en tête qu’il peut refléter une fusion de vues machine et utilisateur
C’est important dans les contextes d’installation, per-user COM, ou d’analyse plus fine.

3.4 InprocServer32

Sous-clé très importante :

Code: Select all

InprocServer32
Elle indique généralement le chemin d’une DLL COM chargée dans le processus client.

Exemple mental :

Code: Select all

C:\MonComposant.dll
Cette sous-clé contient souvent aussi une valeur importante :
  • ThreadingModel
Le ThreadingModel indique comment le composant s’intègre au modèle de threading COM.

Valeurs classiques à connaitre de nom :
  • Apartment
  • Free
  • Both
  • Neutral
On détaillera le threading COM plus tard, mais il faut déjà savoir que cette valeur existe et qu’elle est critique.

3.5 LocalServer32

Si le composant COM n’est pas une DLL in-process mais un serveur local EXE, on trouve souvent :

Code: Select all

LocalServer32
Cette sous-clé pointe vers l’exécutable du serveur COM.

Cela signifie :
  • activation hors processus
  • communication inter-processus
  • coût plus élevé
  • isolation plus forte
Il faut donc distinguer clairement :
  • InprocServer32 → DLL dans le processus client
  • LocalServer32 → EXE séparé
3.6 ProgID

Le ProgID est un nom lisible par l’humain.

Exemple :

Code: Select all

MonApp.Component
Il sert de nom symbolique plus simple que le CLSID.

On peut le voir comme :
  • une couche plus lisible
  • un alias humain vers un CLSID
Mais au final, la vraie identité technique reste le CLSID.

3.7 VersionIndependentProgID

Autre notion utile :

Code: Select all

VersionIndependentProgID
Pourquoi :
  • éviter que le client dépende d’un nom de version trop précis
  • permettre une forme de stabilité logique malgré l’évolution du composant
En gros :
  • ProgID peut être versionné
  • VersionIndependentProgID donne un nom plus stable
3.8 Organisation réelle du Registry COM

Il faut bien retenir la logique suivante :
  • CLSID → identité technique de la classe
  • IID → identité technique de l’interface
  • ProgID → nom humain
  • InprocServer32 / LocalServer32 → localisation du serveur
Autrement dit :

Code: Select all

GUID = identité
Registry = localisation
4) Enregistrement COM

Une classe COM ne “tombe” pas toute seule dans le Registry.
Il faut l’enregistrer.

4.1 regsvr32

Outil classique :

Code: Select all

regsvr32 mon.dll
Ce que beaucoup de gens ignorent, c’est que regsvr32 n’“insère pas magiquement” des clés par lui-même.
Il charge la DLL et appelle une fonction standard exportée par la DLL :

Code: Select all

DllRegisterServer
Autrement dit :
  • regsvr32 charge la DLL
  • la DLL s’enregistre elle-même via son code
4.2 DllRegisterServer et DllUnregisterServer

Fonctions importantes :

Code: Select all

DllRegisterServer
DllUnregisterServer
Elles sont typiques des serveurs COM in-process classiques.

En interne, elles utilisent souvent des APIs registre comme :

Code: Select all

RegCreateKeyEx
RegSetValueEx
RegDeleteKey
RegDeleteTree
RegCloseKey
C’est donc très concret :

Code: Select all

self-registration = la DLL écrit elle-même ses clés COM
4.3 Self-registration : idée et limites

La self-registration est pratique, mais elle a aussi des limites historiques :
  • elle exécute du code arbitraire pendant l’installation
  • elle rend parfois l’installation moins déclarative
  • elle a été critiquée dans certains contextes de packaging ou déploiement
Mais conceptuellement, elle reste très importante pour comprendre le COM classique.

5) OleView : explorer COM

Outil très utile à connaitre :
  • OleView / OleViewDotNet selon les variantes et époques
À quoi ça sert :
  • explorer les CLSID
  • voir les interfaces
  • voir les catégories
  • inspecter l’enregistrement COM
  • mieux comprendre la structure d’un composant
C’est un excellent outil d’exploration quand on apprend COM ou qu’on fait du reverse / diagnostic.

6) Component Categories

COM permet aussi de classer des composants dans des catégories.

Notion importante :
  • CATID
Un CATID est lui aussi un GUID, mais il identifie une catégorie de composants.

Utilité :
  • classer les composants
  • filtrer
  • découvrir certains composants d’une famille donnée
Ce n’est pas le sujet le plus central au début, mais il faut connaitre le concept.

7) La COM Library : runtime de base

COM n’est pas juste un ensemble de GUID et de clés de registre.
Il existe aussi une bibliothèque runtime, souvent appelée la COM Library, qui fournit les services de base nécessaires au modèle.

7.1 Initialisation COM

Avant d’utiliser COM sur un thread, il faut généralement l’initialiser.

APIs importantes :

Code: Select all

CoInitialize(NULL);
CoInitializeEx(NULL, COINIT_MULTITHREADED);
Il faut aussi connaitre :

Code: Select all

CoUninitialize();
Règle critique :
  • COM s’initialise par thread
  • l’initialisation doit être équilibrée par CoUninitialize
Autrement dit :

Code: Select all

1 thread
→ 1 initialisation COM logique
→ CoUninitialize ensuite
7.2 CoInitialize contre CoInitializeEx

Il faut déjà connaitre la différence de philosophie :
  • CoInitialize est la forme historique, souvent associée à l’appartement STA classique
  • CoInitializeEx est plus explicite et plus moderne
Flag important déjà à connaitre :

Code: Select all

COINIT_MULTITHREADED
Plus tard, quand on verra le threading COM, cette partie deviendra encore plus importante. Mais dès maintenant il faut retenir :
  • ne jamais utiliser COM sans penser au threading du thread courant
  • une mauvaise initialisation mène vite à des erreurs comme RPC_E_CHANGED_MODE
8) Memory Management COM

Autre point fondamental : la mémoire.

Dans COM, le client et le serveur peuvent être différents, parfois séparés, parfois compilés différemment, parfois même dans des contextes distincts.
Il faut donc un modèle de gestion mémoire cohérent pour certains buffers échangés.

Solution fournie par COM :

Code: Select all

CoTaskMemAlloc
CoTaskMemFree
CoTaskMemRealloc
Règle très importante :
  • quand COM dit qu’un buffer est alloué avec l’allocateur COM, il doit être libéré avec l’API COM appropriée
  • on ne mélange pas n’importe quel allocateur et n’importe quel free
Principe pratique classique :

Code: Select all

celui qui reçoit un buffer COM doit le libérer selon les règles COM
Cela évite de nombreux problèmes entre client et serveur.

9) Fonctions utiles autour des GUID et des chaînes

En plus de StringFromGUID2, CLSIDFromString et IIDFromString, il faut aussi connaitre :

Code: Select all

StringFromCLSID
StringFromIID
ProgIDFromCLSID
CLSIDFromProgID
Utilité :
  • convertir entre représentation lisible et représentation binaire
  • résoudre un ProgID en CLSID
  • afficher proprement des identités COM
  • outillage et diagnostic
10) Macros d’interface COM

Dans le vieux style COM C / C++, on rencontre aussi des macros comme :

Code: Select all

DECLARE_INTERFACE
DECLARE_INTERFACE_
STDMETHOD
STDMETHOD_
PURE
THIS
THIS_
Pourquoi elles existent :
  • compatibilité C / C++
  • représentation standardisée des interfaces COM
  • portabilité historique du style d’écriture
Ces macros peuvent sembler archaïques au début, mais elles font partie de l’histoire réelle de COM, et on les croise encore dans beaucoup de headers ou vieux codes.

11) Types et constantes à connaitre absolument

Pour ce chapitre, il faut reconnaitre rapidement :
  • HRESULT
  • GUID
  • IID
  • CLSID
  • REFGUID
  • REFIID
  • REFCLSID
  • CATID
  • ULONG
  • HKEY
Et côté constantes / macros :
  • S_OK
  • S_FALSE
  • E_FAIL
  • E_OUTOFMEMORY
  • E_POINTER
  • E_NOINTERFACE
  • E_NOTIMPL
  • SUCCEEDED
  • FAILED
  • MAKE_HRESULT
  • FACILITY_ITF
12) APIs à connaitre absolument

Pour HRESULT / GUID / COM Library / Registry, il faut déjà connaitre :
  • CoCreateGuid
  • IsEqualGUID
  • StringFromGUID2
  • StringFromCLSID
  • StringFromIID
  • CLSIDFromString
  • IIDFromString
  • CLSIDFromProgID
  • ProgIDFromCLSID
  • CoInitialize
  • CoInitializeEx
  • CoUninitialize
  • CoTaskMemAlloc
  • CoTaskMemFree
  • RegCreateKeyEx
  • RegSetValueEx
  • RegDeleteKey
  • RegCloseKey
Et côté export d’enregistrement COM classique :
  • DllRegisterServer
  • DllUnregisterServer
13) Erreurs classiques du débutant

Comme souvent en COM, les erreurs viennent d’un mauvais modèle mental de base.

Parmi les fautes classiques :
  • penser qu’un HRESULT se teste uniquement avec hr == S_OK
  • oublier que S_FALSE est un succès
  • confondre CLSID et IID
  • croire qu’un GUID est juste une chaîne texte
  • oublier que le Registry sert à localiser dynamiquement le composant
  • utiliser COM sans CoInitialize / CoInitializeEx
  • oublier CoUninitialize
  • mélanger les allocateurs mémoire hors règles COM
  • croire que regsvr32 “écrit des clés magiquement” sans passer par le code de la DLL
14) Ce qu’il faut retenir

Si on résume les fondations de ce chapitre :
  • HRESULT = système d’erreur riche, extensible et binaire
  • GUID = identité globale unique
  • CLSID = identité d’une classe COM
  • IID = identité d’une interface COM
  • Registry = localisation dynamique des composants
  • COM Library = runtime de base qu’il faut initialiser par thread
Vision mentale globale :

Code: Select all

HRESULT
→ comment COM signale le résultat

GUID
→ comment COM identifie les choses

Registry
→ comment Windows retrouve le composant

COM Library
→ comment le runtime met tout cela en route
Conclusion

Ce chapitre est vraiment fondamental, parce qu’il donne les bases “invisibles” de COM. Avant même de créer un objet, il faut savoir comment COM exprime les erreurs, comment il identifie une classe ou une interface, comment Windows localise le bon serveur, et quel runtime doit être initialisé pour que tout cela fonctionne.

Si tu maitrises déjà correctement :
  • HRESULT et les macros SUCCEEDED / FAILED
  • la différence entre succès et simple booléen
  • GUID, CLSID, IID et CATID
  • la structure du Registry COM autour de HKCR\CLSID
  • InprocServer32, LocalServer32, ProgID, VersionIndependentProgID
  • CoInitialize / CoInitializeEx / CoUninitialize
  • CoTaskMemAlloc / CoTaskMemFree
  • les conversions GUID ↔ string
alors tu comprends déjà une grosse partie de l’infrastructure réelle de COM.

Et ça, c’est exactement ce qu’il faut avant d’attaquer le prochain gros bloc :
  • les class factories
  • CoCreateInstance
  • l’activation COM
  • et la création réelle des objets
A bientot pour la suite sur COM et le dev Win32 8-)

Who is online

Users browsing this forum: No registered users and 0 guests