Les bases de COM

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:

Les bases de COM

Post by Hydraxx »

Salut, :D

aujourd’hui on va voir un composant fondamental de l’architecture Windows : COM, pour Component Object Model.

C’est un sujet extrêmement important, parce qu’en pratique beaucoup de développeurs utilisent déjà COM sans toujours s’en rendre compte. Dès que tu touches à certaines API Windows un peu sérieuses, tu finis très souvent par tomber sur du COM, directement ou indirectement.

On retrouve COM dans énormément de domaines :
  • DirectX
  • Media Foundation
  • Windows Shell
  • WMI
  • extensions Explorer
  • certaines APIs d’administration
  • beaucoup de technologies Microsoft historiques et modernes
Le problème, c’est que COM fait parfois peur au début, surtout à cause du vocabulaire, des GUID, des interfaces, du ref counting, des factories, des apartments, et de toute l’image “vieille techno obscure” qu’il traine chez certains développeurs. Pourtant, les bases de COM sont en réalité très logiques si on les aborde dans le bon ordre.

Dans ce cours on va donc poser une base propre, claire et vraiment utile, dans un style développement système. Le but n’est pas juste de retenir trois noms, mais de comprendre comment COM fonctionne vraiment au niveau binaire et pourquoi il a été si important dans l’écosystème Windows.

1) C’est quoi COM

COM signifie Component Object Model.

COM est un modèle de composants binaires.
Le mot important ici, c’est binaire.

Cela veut dire que COM ne définit pas seulement une manière “logique” d’écrire du code objet. Il définit surtout une manière stable au niveau binaire pour que des composants puissent communiquer entre eux, meme s’ils ont été compilés séparément, développés séparément, ou utilisés depuis des langages différents.

Autrement dit, COM sert à faire dialoguer des composants logiciels selon un contrat binaire très précis.

L’idée générale est la suivante :
  • une application n’est plus forcément un gros bloc unique
  • elle peut être composée de plusieurs composants
  • chaque composant expose des interfaces
  • le client consomme ces interfaces sans connaitre l’implémentation réelle
C’est une idée très importante dans l’histoire de Windows et dans toute la philosophie de réutilisation logicielle Microsoft.

2) Pourquoi COM a existé : le problème des applications monolithiques

Avant de comprendre COM, il faut comprendre le problème qu’il voulait résoudre.

Dans un modèle monolithique classique, on a souvent :
  • un seul gros exécutable
  • peu de séparation nette entre les parties
  • faible réutilisation
  • fort couplage entre les composants internes
Conséquences :
  • modifier une petite partie peut imposer de recompiler ou redistribuer tout le programme
  • réutiliser une fonctionnalité dans un autre logiciel devient pénible
  • faire dialoguer proprement deux logiciels ou deux modules devient difficile
Le modèle composant cherche au contraire à permettre :
  • le remplacement d’un composant
  • la réutilisation d’un composant
  • la séparation claire entre interface publique et implémentation privée
C’est exactement sur ce terrain que COM se place.

3) L’idée centrale : programmer contre une interface, pas contre l’objet concret

En COM, le client ne travaille jamais “directement” avec l’objet concret comme dans une vision naïve de la programmation objet.

Il travaille avec une interface.

Le schéma mental de base est :

Code: Select all

objet COM
↓
interface
↓
méthodes exposées
Le client voit donc uniquement :
  • un contrat
  • une liste de fonctions
  • un type d’interface
Il ne voit pas :
  • la structure interne de l’objet
  • ses données privées
  • son implémentation réelle
  • sa classe concrète au sens C++ habituel
Cette séparation est le cœur du modèle.

4) Une interface COM : ce que c’est réellement

Dans COM, une interface est une table de méthodes accessible via un pointeur.

D’un point de vue conceptuel :
  • une interface expose des fonctions
  • elle ne contient pas de données publiques
  • elle n’expose pas l’implémentation
  • elle décrit uniquement ce que le composant sait faire
En C++, on la représente souvent comme une classe virtuelle pure :

Code: Select all

struct IX
{
    virtual void Fx() = 0;
};
Mais il faut faire très attention à une chose :

Code: Select all

COM n’est pas “juste du C++”
COM est un contrat binaire.
Le C++ est juste un moyen pratique de représenter ce contrat quand on écrit du code C++.

Dans un autre langage, ou meme en C pur, le modèle reste le meme au niveau binaire.

5) Pourquoi COM est un modèle binaire et pas seulement objet

C’est probablement le point le plus important à comprendre.

Si COM n’était qu’un simple style de classes C++, il ne servirait pas à grand-chose au niveau système. Ce qui le rend puissant, c’est qu’il fixe une représentation binaire suffisamment stable pour permettre l’interopérabilité.

Cela implique notamment :
  • les interfaces sont identifiées de manière unique
  • les méthodes sont accessibles selon un ordre binaire déterminé
  • le client n’a pas besoin de connaitre la classe interne
  • des composants compilés séparément peuvent coopérer
C’est aussi pour ça que COM a été si important pour :
  • les DLL réutilisables
  • les composants système Windows
  • les objets Shell
  • l’automation
  • les technologies Microsoft construites au-dessus
6) Les GUID, IID et CLSID : identité des interfaces et des classes

Dans COM, les choses ne sont pas identifiées par de simples noms texte.

On utilise des identifiants globaux uniques, les GUID.

Types importants à connaitre :
  • GUID
  • IID
  • CLSID
Le principe :
  • une interface possède un IID : Interface Identifier
  • une classe COM possède un CLSID : Class Identifier
Pourquoi c’est utile :
  • pas de collision de noms
  • identification binaire claire
  • résolution stable des interfaces et des classes
Autrement dit, quand on demande une interface à un objet COM, on ne dit pas “donne-moi celle qui s’appelle IX” au sens symbolique simpliste. On demande l’interface correspondant à un identifiant unique.

7) Accès à un objet COM

Un objet COM n’est jamais utilisé directement “à mains nues”.

On obtient toujours un pointeur vers une interface.

Le modèle mental à retenir est :

Code: Select all

objet COM
↓
pointeur d’interface
↓
appel de méthodes via cette interface
Le client ne possède donc jamais l’objet au sens brut. Il possède une vue contrôlée sur lui.

C’est extrêmement important, parce que cela permet à un même objet de présenter plusieurs interfaces différentes selon les besoins.

8) Toutes les interfaces COM héritent de IUnknown

En COM, toutes les interfaces dérivent de l’interface de base :

Code: Select all

IUnknown
C’est la racine du modèle COM.

Elle définit trois fonctions fondamentales :
  • QueryInterface
  • AddRef
  • Release
Ces trois fonctions sont absolument centrales.
Si tu comprends vraiment leur rôle, tu comprends déjà une grosse partie du fonctionnement réel de COM.

9) Pourquoi IUnknown est si important

IUnknown sert à trois choses fondamentales :
  • naviguer entre interfaces
  • gérer la durée de vie de l’objet
  • fournir une base commune à tout objet COM
Autrement dit :
  • QueryInterface sert à demander une autre interface
  • AddRef augmente le compteur de références
  • Release diminue le compteur de références
C’est à la fois un mécanisme d’identité et un mécanisme de gestion mémoire.

10) Signature classique de IUnknown

En C++, on voit souvent quelque chose comme :

Code: Select all

struct IUnknown
{
    virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};
Il faut connaitre aussi plusieurs types COM très fréquents :
  • HRESULT
  • ULONG
  • REFIID
  • IID
  • CLSID
On retient déjà ici deux idées :
  • COM utilise massivement HRESULT pour exprimer succès ou erreur
  • la gestion mémoire classique passe par un reference counting
11) QueryInterface : demander une interface

QueryInterface est la méthode qui permet de demander à un objet :

Code: Select all

“exposes-tu telle interface ?”
Exemple conceptuel :

Code: Select all

obj->QueryInterface(IID_IX, (void**)&px);
Si l’objet supporte l’interface demandée :
  • il retourne S_OK
  • il place dans le pointeur de sortie l’adresse de l’interface demandée
  • il incrémente aussi le ref count sur l’interface retournée
Sinon il retourne :

Code: Select all

E_NOINTERFACE
C’est un point fondamental :
  • on ne caste pas brutalement un objet COM comme on le ferait parfois en C++
  • on demande explicitement une interface
12) Pourquoi QueryInterface est si puissant

QueryInterface permet la navigation entre interfaces.

Exemple mental :

Code: Select all

objet COM
├── IX
├── IY
└── IZ
Si tu possèdes un pointeur sur IX, tu peux demander IY ou IZ via QueryInterface, si l’objet les expose.

Cela donne un modèle très souple :
  • le client commence avec une interface
  • il découvre ensuite les autres interfaces disponibles
  • il ne dépend pas d’une classe concrète
C’est une idée très élégante dans l’architecture COM.

13) Les règles fondamentales de QueryInterface

QueryInterface doit respecter des règles très précises.

Reflexive

Une interface doit toujours pouvoir se retourner elle-même.

Autrement dit :

Code: Select all

si tu possèdes IX
alors QueryInterface(IID_IX) doit réussir
Symmetric

Si IX permet d’obtenir IY, alors IY doit permettre d’obtenir IX.

Transitive

Si IX permet d’obtenir IY, et IY permet d’obtenir IZ, alors IX doit permettre d’obtenir IZ.

Ces règles sont très importantes, parce qu’elles donnent une cohérence forte au graphe des interfaces d’un objet COM.

Ce n’est pas du “détail de théorie”.
C’est un vrai contrat de cohérence de l’objet.

14) AddRef : augmentation du compteur de références

AddRef sert à augmenter le compteur de références de l’objet.

Exemple :

Code: Select all

p->AddRef();
Le principe est simple :
  • tant qu’il existe des clients qui utilisent l’objet, le compteur reste > 0
  • chaque nouvelle référence valide doit être comptée
En COM, obtenir une nouvelle interface valide implique souvent un AddRef implicite ou explicite selon le chemin exact.

Le but est clair :

Code: Select all

l’objet reste vivant tant qu’il est encore référencé
15) Release : diminution du compteur de références

Release fait l’opération inverse :

Code: Select all

p->Release();
Le compteur diminue.

Quand il atteint zéro :
  • l’objet se détruit lui-meme
  • sa mémoire est libérée
  • il cesse d’exister
C’est la base de la gestion mémoire COM.

Très important :
  • un oubli de Release produit une fuite
  • un Release en trop peut détruire l’objet trop tôt
Donc le ref counting est simple en théorie, mais il faut le manipuler avec discipline.

16) COM et gestion mémoire : différent du C++ classique

Dans du C++ classique, on pense souvent en termes de :
  • new / delete
  • constructeur / destructeur
  • smart pointers du langage
En COM, la durée de vie observable depuis le client est surtout contrôlée par :
  • AddRef
  • Release
Cela veut dire que le client COM doit raisonner en références, pas seulement en “je possède un objet concret”.

C’est pour ça que les wrappers modernes utilisent souvent des smart pointers COM spécialisés, mais conceptuellement, la base reste toujours AddRef / Release.

17) Exemple mental simple du ref counting

Imaginons :
  • un objet est créé avec refcount = 1
  • un client obtient une autre interface → refcount = 2
  • le premier client fait Release → refcount = 1
  • le second client fait Release → refcount = 0
  • l’objet est détruit
Ce modèle est très important à visualiser mentalement.

18) Un objet COM peut exposer plusieurs interfaces

C’est une idée centrale de COM.

Un même objet peut exposer plusieurs vues fonctionnelles, chacune correspondant à une interface différente.

Exemple :

Code: Select all

objet
├── IX
├── IY
└── IZ
Chaque interface représente une facette différente du même objet.

Pourquoi c’est puissant :
  • on sépare mieux les responsabilités
  • on cache certaines capacités à certains clients
  • on garde un modèle modulaire
  • on fait évoluer un composant en ajoutant des interfaces
19) Interface pointer ≠ objet complet

C’est une distinction absolument essentielle.

Quand tu manipules un pointeur COM, tu manipules :

Code: Select all

un pointeur d’interface
et pas “l’objet complet” au sens naïf.

Cela explique pourquoi :
  • tu dois passer par QueryInterface
  • tu n’as pas accès à l’implémentation concrète
  • deux pointeurs d’interface différents peuvent référencer le même objet sous des vues différentes
C’est une différence conceptuelle majeure par rapport à certaines approches C++ plus directes.

20) HRESULT : le modèle d’erreur COM

COM utilise massivement HRESULT.

C’est le type de retour standard pour de nombreuses méthodes COM.

Valeurs classiques à connaitre :
  • S_OK
  • S_FALSE
  • E_NOINTERFACE
  • E_POINTER
  • E_FAIL
  • E_OUTOFMEMORY
  • E_INVALIDARG
  • E_ACCESSDENIED
  • CLASS_E_CLASSNOTAVAILABLE
  • REGDB_E_CLASSNOTREG
Pourquoi c’est important :
  • COM n’utilise pas le modèle d’exception C++ comme base du protocole
  • il repose sur des codes de retour explicites
  • il faut toujours vérifier les HRESULT
Macro de base importante :
  • SUCCEEDED(hr)
  • FAILED(hr)
Exemple :

Code: Select all

HRESULT hr = obj->QueryInterface(IID_IX, (void**)&px);
if (FAILED(hr))
{
    // gestion d'erreur
}
21) COM et vtable : compréhension binaire minimale

Même si on reste ici sur les bases, il faut déjà comprendre une chose :

Code: Select all

une interface COM est essentiellement une table de fonctions
Autrement dit, quand on appelle une méthode COM, on appelle en pratique une entrée de vtable selon un ordre binaire bien défini.

C’est précisément ce qui rend COM :
  • compatible binaire
  • interopérable entre composants
  • lisible aussi en reverse engineering
Pour un développeur système ou un reverseur, c’est une notion importante. COM n’est pas “magique”. C’est une convention binaire stricte.

22) Création d’objet COM : aperçu du problème

À ce stade, une question logique arrive :

Code: Select all

comment obtient-on le tout premier pointeur d’interface ?
C’est précisément le sujet des class factories, de CoCreateInstance, de CoGetClassObject, et du mécanisme d’activation COM.

On n’entre pas encore à fond dedans ici, mais il faut déjà retenir que :
  • le client ne construit pas directement l’objet avec new
  • la création passe par l’infrastructure COM
  • l’objet est identifié par un CLSID
  • l’interface initiale demandée est identifiée par un IID
C’est ce que nous verrons ensuite.

23) In-process, out-of-process et localité du composant

Même pour un cours de base, il est bon de savoir qu’un objet COM n’est pas forcément dans le même module ou le même processus que son client.

Un composant COM peut être :
  • in-process : typiquement une DLL chargée dans le processus client
  • local server : EXE COM dans un autre processus local
  • remote : selon les technologies distribuées plus avancées
C’est important, parce que le modèle interface / IUnknown / QueryInterface / AddRef / Release reste valable, meme si la mise en œuvre derrière devient beaucoup plus complexe.

24) Pourquoi COM a autant compté dans Windows

COM a servi de base ou de sous-couche à énormément de technologies Microsoft.

Parmi les domaines où tu rencontres COM ou ses dérivés logiques :
  • Shell Windows
  • WMI
  • DirectX classique
  • Media Foundation
  • certaines APIs audio / vidéo
  • Automation
  • beaucoup de composants internes historiques de Windows
Donc apprendre COM n’est pas “apprendre une vieille techno morte”.
C’est comprendre une partie très profonde de l’écosystème Windows.

25) Ce qu’il faut retenir sur les bases de COM

Si on résume les idées les plus importantes du cours :
  • COM est un modèle de composants binaires
  • il permet à des composants logiciels de dialoguer via des interfaces stables
  • le client ne voit jamais directement l’implémentation
  • tout passe par des pointeurs d’interface
  • toutes les interfaces COM héritent de IUnknown
  • IUnknown expose QueryInterface, AddRef et Release
  • QueryInterface permet de naviguer entre interfaces
  • AddRef et Release contrôlent la durée de vie de l’objet
  • les interfaces sont identifiées par des IID
  • les classes COM sont identifiées par des CLSID
26) APIs, types et constantes à connaitre absolument

Pour avoir déjà une bonne base, il faut reconnaitre rapidement :
  • IUnknown
  • HRESULT
  • GUID
  • IID
  • CLSID
  • REFIID
  • REFCLSID
  • ULONG
  • S_OK
  • S_FALSE
  • E_NOINTERFACE
  • SUCCEEDED
  • FAILED
Et côté fonctions / concepts :
  • QueryInterface
  • AddRef
  • Release
Pour la suite du parcours COM, il faudra ensuite connaitre :
  • CoInitializeEx
  • CoUninitialize
  • CoCreateInstance
  • CoGetClassObject
  • IClassFactory
Mais pour ce cours, le noyau dur reste IUnknown et le modèle interface + ref counting.

27) Erreurs classiques des débutants en COM

Parmi les erreurs fréquentes :
  • croire qu’un objet COM se manipule comme un objet C++ ordinaire
  • oublier qu’on travaille via des interfaces
  • ne pas comprendre le rôle exact de QueryInterface
  • oublier un Release
  • faire un Release de trop
  • penser que COM se résume à “des GUID bizarres”
  • ne pas vérifier les HRESULT
  • confondre classe COM et interface COM
Ces erreurs viennent presque toujours d’un mauvais modèle mental de départ.

28) Conclusion

COM est l’un des grands fondements historiques et techniques de Windows. Ce n’est pas seulement un vieux mécanisme de composants : c’est un modèle binaire extrêmement important pour comprendre comment beaucoup de technologies Windows exposent leurs objets, leurs services, et leurs capacités.

Si tu comprends déjà vraiment :
  • ce qu’est une interface COM
  • pourquoi tout passe par IUnknown
  • à quoi servent QueryInterface, AddRef et Release
  • la différence entre objet concret et pointeur d’interface
  • la logique des IID et CLSID
alors tu as déjà compris le cœur du modèle de base.

Et c’est exactement ce cœur qui permet ensuite d’aborder sans se noyer :
  • les class factories
  • CoCreateInstance
  • les serveurs COM
  • l’initialisation COM
  • le threading COM
  • les technologies Windows construites au-dessus
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