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
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;
Code: Select all
int result;
- pas d’origine
- pas de catégorie
- pas de code précis
- pas de nuance entre plusieurs formes de succès
- local ou distant
- in-process ou out-of-process
- lié à des APIs Win32
- lié à RPC
- lié à OLE
- lié à l’interface elle-même
La réponse est :
Code: Select all
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 |
- bit de sévérité
- bits de facility
- code d’erreur ou de succès
- Severity indique succès ou échec
- Facility indique d’où vient le code
- Code indique la valeur concrète
Code: Select all
0x80070005
- 0x8... → bit d’erreur positionné, donc échec
- facility 7 → origine Win32
- code 5 → Access Denied
Code: Select all
HRESULT = erreur Win32 Access Denied emballée au format COM
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
- 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
C’est l’un des pièges les plus classiques du débutant :
Code: Select all
S_FALSE = succès
Si tu fais :
Code: Select all
if (hr == S_OK)
La bonne approche est :
Code: Select all
if (SUCCEEDED(hr))
Code: Select all
if (FAILED(hr))
Code: Select all
SUCCEEDED(hr)
FAILED(hr)
- 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
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
Code: Select all
COM ≠ booléen
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
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
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)
- 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
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é
- gérer les HRESULT connus
- mais aussi rester robuste face à des HRESULT inconnus
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
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é ?”
Code: Select all
“de quoi parle-t-on exactement ?”
GUID signifie Globally Unique Identifier.
C’est un identifiant 128 bits.
Représentation classique :
Code: Select all
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Code: Select all
12345678-1234-1234-ABCD-1234567890AB
Code: Select all
GUID = identité binaire globale
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
COM veut une identité :
- unique
- stable
- indépendante des noms symboliques
- indépendante des collisions de chaînes
Code: Select all
GUID
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
- CLSID = quel composant concret
- IID = quelle interface
- CATID = quelle famille / catégorie logique
2.4 Génération d’un GUID
API importante :
Code: Select all
CoCreateGuid
Code: Select all
GUID g;
HRESULT hr = CoCreateGuid(&g);
- guidgen.exe
- Visual Studio
- certains assistants de génération de GUID
- on ne “fabrique” pas un GUID à la main
- on le génère avec les outils ou APIs prévus
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;
- la représentation texte est une vue humaine
- la structure GUID est la forme binaire réelle
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(...)
Code: Select all
INITGUID
2.7 Comparer des GUID
API / macro importante :
Code: Select all
IsEqualGUID
Exemple logique :
Code: Select all
if (IsEqualGUID(iid, IID_IMonInterface))
{
// interface reconnue
}
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;
- éviter des copies inutiles
- garder une convention d’appel stable
- améliorer l’efficacité et la clarté des signatures
2.9 Conversion GUID ↔ chaîne
APIs très utiles :
Code: Select all
StringFromGUID2
CLSIDFromString
IIDFromString
StringFromCLSID
StringFromIID
- affichage lisible pour debug
- lecture depuis configuration ou registry
- outillage COM
- interop avec chaînes
- GUID → string pour affichage ou registry
- string → GUID pour résolution ou parsing
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 ?
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
Il relie :
- un CLSID
- à des informations techniques sur le composant
- comme son chemin DLL ou EXE
- son mode de threading
- ses noms lisibles éventuels
La vue logique classique est :
Code: Select all
HKEY_CLASSES_ROOT
└── CLSID
└── {GUID}
├── InprocServer32
├── LocalServer32
├── ProgID
├── VersionIndependentProgID
└── autres sous-clés
- la clé CLSID est centrale
- chaque CLSID a sa sous-clé
- les sous-clés décrivent comment activer le composant
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
- 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
3.4 InprocServer32
Sous-clé très importante :
Code: Select all
InprocServer32
Exemple mental :
Code: Select all
C:\MonComposant.dll
- ThreadingModel
Valeurs classiques à connaitre de nom :
- Apartment
- Free
- Both
- Neutral
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
Cela signifie :
- activation hors processus
- communication inter-processus
- coût plus élevé
- isolation plus forte
- InprocServer32 → DLL dans le processus client
- LocalServer32 → EXE séparé
Le ProgID est un nom lisible par l’humain.
Exemple :
Code: Select all
MonApp.Component
On peut le voir comme :
- une couche plus lisible
- un alias humain vers un CLSID
3.7 VersionIndependentProgID
Autre notion utile :
Code: Select all
VersionIndependentProgID
- é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
- ProgID peut être versionné
- VersionIndependentProgID donne un nom plus stable
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
Code: Select all
GUID = identité
Registry = localisation
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
Il charge la DLL et appelle une fonction standard exportée par la DLL :
Code: Select all
DllRegisterServer
- regsvr32 charge la DLL
- la DLL s’enregistre elle-même via son code
Fonctions importantes :
Code: Select all
DllRegisterServer
DllUnregisterServer
En interne, elles utilisent souvent des APIs registre comme :
Code: Select all
RegCreateKeyEx
RegSetValueEx
RegDeleteKey
RegDeleteTree
RegCloseKey
Code: Select all
self-registration = la DLL écrit elle-même ses clés COM
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
5) OleView : explorer COM
Outil très utile à connaitre :
- OleView / OleViewDotNet selon les variantes et époques
- explorer les CLSID
- voir les interfaces
- voir les catégories
- inspecter l’enregistrement COM
- mieux comprendre la structure d’un composant
6) Component Categories
COM permet aussi de classer des composants dans des catégories.
Notion importante :
- CATID
Utilité :
- classer les composants
- filtrer
- découvrir certains composants d’une famille donnée
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);
Code: Select all
CoUninitialize();
- COM s’initialise par thread
- l’initialisation doit être équilibrée par CoUninitialize
Code: Select all
1 thread
→ 1 initialisation COM logique
→ CoUninitialize ensuite
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
Code: Select all
COINIT_MULTITHREADED
- ne jamais utiliser COM sans penser au threading du thread courant
- une mauvaise initialisation mène vite à des erreurs comme RPC_E_CHANGED_MODE
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
- 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
Code: Select all
celui qui reçoit un buffer COM doit le libérer selon les règles COM
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
- convertir entre représentation lisible et représentation binaire
- résoudre un ProgID en CLSID
- afficher proprement des identités COM
- outillage et diagnostic
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_
- compatibilité C / C++
- représentation standardisée des interfaces COM
- portabilité historique du style d’écriture
11) Types et constantes à connaitre absolument
Pour ce chapitre, il faut reconnaitre rapidement :
- HRESULT
- GUID
- IID
- CLSID
- REFGUID
- REFIID
- REFCLSID
- CATID
- ULONG
- HKEY
- S_OK
- S_FALSE
- E_FAIL
- E_OUTOFMEMORY
- E_POINTER
- E_NOINTERFACE
- E_NOTIMPL
- SUCCEEDED
- FAILED
- MAKE_HRESULT
- FACILITY_ITF
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
- DllRegisterServer
- DllUnregisterServer
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
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
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
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
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
