[c/win32/wmi]savoir si une classe com est installée

Description

Pour répondre à la question d'un boulay (Pas sur CS).

Il voulait savoir comment faire pour déterminer si une classe COM précise est présente ou non.

Je poste car il y a quand même une ou deux lignes pas inintéressantes.
La requête WMI avec parcourt d'énumération en pur C par exemple.
WMI est particulièrement lent, mais propose quand même une quantité impressionnante d'informations sur l'OS, le PC...

Ce source propose plus ou moins trois méthodes tournant autour du sujet.
Le programme peut prendre en entrée un ProgId ou un CLSID.
Un ProgId, ça ressemble à ça :
InternetExplorer.Application

Un CLSID à ça :
{0002DF01-0000-0000-C000-000000000046}

Les CLSID ont été mis en place pour être vraiment sur que deux entreprises ne décident pas de faire deux composants COM avec le même nom.
Cela aurait provoquer des problèmes de conflit évidents...
Les CLSID sont en effet théoriquement unique (Basés sur l'adresse MAC du PC, de la date et de l'heure...)
Lorsque l'on veut consommer un composant COM, on peut passer par sont CLSID ou sont ProgId, au choix. Le CLSID étant donc plus sûr.

Ensuite, trois méthodes de validation sont disponibles :

1) Recherche dans le registre, et vérification de l'existence du fichier.

L'installation d'une classe COM se fait généralement par l'appel d'une fonction exportée par le fichier (.dll, .ocx...) contenant la classe.
Cette fonction s'appelle DllRegisterServer (Et son pendant DllUnregisterServer).
C'est tout simplement ce que fait l'utilitaire de registration de classe COM regsvr32, il appelle simplement DllRegisterServer.
Le code de DllRegisterServer installe donc la (ou les) classe(s) COM contenue(s) dans le fichier .dll/.ocx...
Elle va en fait mettre à jour la base de registre, principalement dans :
HKEY_LOCAL_MACHINE\SOFTWARE\Classes
(Equivalent de HKEY_CLASSES_ROOT, obsolète)

Elle va y ajouter une clé du nom du ProgId de la classe.
En sous clé de cette clé, elle va mettre une clé CLSID, qui contiendra comme valeur par défaut le CLSID de la classe.

Et dans :
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID

Elle ajoute une clé du nom du CLSID.
Cette clé contient :
une sous clé ProgID qui contient le ProgId comme valeur par défaut.
généralement une sous clé InprocServer32 avec comme valeur par défaut le chemin complet vers le .ocx/.dll.
si la classe COM est proposée par un .exe, alors c'est une sous clé LocalServer32 avec comme valeur par défaut le chemin complet vers le .exe.

La première méthode consiste donc à aller voir dans le registre si tout cela est présent, et si le fichier .ocx/.dll/.exe existe bien.

2) Listage des classes COM via WMI :

WMI permet de lister les classes COM installées.

WMI propose en effet la classe Win32_ClassicCOMClass :
http://msdn.microsoft.com/en-us/library/aa394086(VS.85).aspx

Il suffit de faire un select * sur cette classe pour récupérer un énumérateur sur toutes les classes COM disponible.
On peut comparer les CLSID de ces classes COM avec celui que l'on cherche.

3) Tentative via CoCreateInstance :

On peut tout bêtement essayer de faire un CoCreateInstance, et regarder si ça a marché ou non !

4) Il y a sûrment pas mal des méthodes dérivées :

Par exemple en passant par CoGetClassObject au lieu de CoCreateInstance.
Ou encore en cherchant le ProgId directement dans WMI, sans le traduire en CLSID puis rechercher le CLSID dans WMI.
...

Source / Exemple :


#include "checkexistence.h"

/**

  • Trouve le nom du fichier dans la base de registre et vérifie son existance
  • /
BOOL __stdcall CheckFromRegistry(TCHAR *lpClsid, HWND hWnd) { TCHAR lpBuffer[512]; /* Tampon à usage divers */ TCHAR lpFile[512]; /* Fichier contenant la classe */ TCHAR lpErrorMsg[512]; /* Message d'erreur */ HKEY hClsid; /* Handle sur la clé correspondant au CLSID */ HKEY hServer32; /* Handle sur la clé du *Server32 */ DWORD nBufferSize; /* Taille du tampon pour la récupération du CLSID */ LONG nReturnedValue; /* Valeur renvoyée par les appels */ BOOL bResult; int nI; bResult = FALSE; lstrcpy(lpBuffer, _T("SOFTWARE\\Classes\\CLSID\\")); lstrcat(lpBuffer, lpClsid); /* classes\CLSID\lpClsid */ nReturnedValue = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpBuffer, 0, KEY_READ, &hClsid); if (nReturnedValue) { SetLastError(nReturnedValue); Sys_ShowLastError(); lstrcpy(lpErrorMsg, _T("Cannot open key: \"HKEY_LOCAL_MACHINE\\")); lstrcat(lpErrorMsg, lpBuffer); lstrcat(lpErrorMsg, _T("\"")); MessageBox(hWnd, lpErrorMsg, NULL, MB_OK | MB_ICONERROR); goto the_end; } /* InProcServer32 et LocalServer32 */ if (RegOpenKeyEx(hClsid, _T("InProcServer32"), 0, KEY_READ, &hServer32)) { if (RegOpenKeyEx(hClsid, _T("LocalServer32"), 0, KEY_READ, &hServer32)) { lstrcpy(lpErrorMsg, _T("Cannot find sub key InProcServer32 or ")); lstrcat(lpErrorMsg, _T("LocalServer32 for key: \"HKEY_LOCAL_MACHINE\\")); lstrcat(lpErrorMsg, lpBuffer); lstrcat(lpErrorMsg, _T("\"")); MessageBox(hWnd, lpErrorMsg, NULL, MB_OK | MB_ICONERROR); goto close_clsid; } } /* Récupération du nom du fichier */ nBufferSize = 512; nReturnedValue = RegQueryValueEx(hServer32, NULL, NULL, NULL, (BYTE*)lpBuffer, &nBufferSize); if (nReturnedValue) { lstrcpy(lpErrorMsg, _T("Cannot read default value of key:\"HKEY_LOCAL_MACHINE\\")); lstrcat(lpErrorMsg, lpBuffer); lstrcat(lpErrorMsg, _T(")")); MessageBox(hWnd, lpErrorMsg, NULL, MB_OK | MB_ICONERROR); goto close_server32; } /* On résoud les variables d'environnement */ ExpandEnvironmentStrings(lpBuffer, lpFile, 512); /* On enlève les quotes */ if (lpFile[0] == '\"') { lstrcpy(lpFile, lpFile + 1); nI = 0; while (lpFile[nI]) nI++; lpFile[nI - 1] = '\0'; } /* Vérification de l'existance du fichier */ if (! FileSys_FileExists(lpFile)) { lstrcpy(lpErrorMsg, _T("File: \"")); lstrcat(lpErrorMsg, lpFile); lstrcat(lpErrorMsg, _T("\" does not exist.")); MessageBox(hWnd, lpErrorMsg, NULL, MB_OK | MB_ICONERROR); goto close_server32; } bResult = TRUE; close_server32: RegCloseKey(hServer32); close_clsid: RegCloseKey(hClsid); the_end: return bResult; } /**
  • Cherche dans une énumération renvoyée par WMI
  • /
BOOL __stdcall CheckWithWmi(TCHAR *lpClsid, HWND hWnd) { IWbemLocator *lpLocator; /* Pour se connecter et la récup les services */ IWbemServices *lpServices; /* Accès aux services WMI */ BSTR lpConnectionString; /* Chaîne de connexion à CMIV2 */ IEnumWbemClassObject *lpEnum; /* Enumérateur sur les classes COM */ BSTR lpWQL; /* Langage de la query : WMI Query Language */ BSTR lpRequest; /* Requête de récupération des classes COM */ HRESULT nReturnValue; /* Vérification du retour du next */ IWbemClassObject *lpComClass; /* L'objet WMI correspondant à la calsse COM */ DWORD nReturned; /* Nombre d'objets retournés par le Next */ BOOL bContinue; /* Continuer l'énumération ? */ VARIANT clsidVar; /* Variant récupérant le CLSID de la classe */ TCHAR lpCurrentClsid[256]; /* Clsid courant dans l'énumération */ BOOL bResult; bResult = FALSE; /* On récupère le locator */ if (CoCreateInstance(&CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, (void**)&lpLocator)) { MessageBox(hWnd, _T("Error on CoCreateInstance of WbemLocator"), NULL, MB_OK | MB_ICONERROR); goto the_end; } /* On se connecte au PC local */ lpConnectionString = SysAllocString(L"ROOT\\CIMV2"); if (lpLocator->lpVtbl->ConnectServer(lpLocator, lpConnectionString, NULL, NULL, NULL, 0, NULL, NULL, &lpServices)) { MessageBox(hWnd, _T("Error in ConnectServer"), NULL, MB_OK | MB_ICONERROR); goto free_connection_string; } /* Configuration de la sécurité du proxy */ if (CoSetProxyBlanket((IUnknown*)lpServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE)) { MessageBox(hWnd, _T("Error in CoSetProxyBlanket"), NULL, MB_OK | MB_ICONERROR); goto release_services; } /* Exécution de la requête pour récupérer un énumérateur sur les classes */ lpWQL = SysAllocString(L"WQL"); lpRequest = SysAllocString(L"SELECT * FROM Win32_ClassicCOMClass"); if (lpServices->lpVtbl->ExecQuery(lpServices, lpWQL, lpRequest, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &lpEnum)) { MessageBox(hWnd, _T("Error during query execution"), NULL, MB_OK | MB_ICONERROR); goto free_request; } /* Parcourt des classes COM */ bContinue = TRUE; do { nReturnValue = lpEnum->lpVtbl->Next(lpEnum, WBEM_INFINITE, 1, &lpComClass, &nReturned); switch (nReturnValue) { case WBEM_S_NO_ERROR: /* Récupération du CLSID comme propriété ComponentId dans un VARIANT */ lpComClass->lpVtbl->Get(lpComClass, L"ComponentId", 0, &clsidVar, NULL, NULL); /* Passage en TCHAR* pour la comparaison */ #ifndef UNICODE WideCharToMultiByte(CP_ACP, 0, clsidVar.bstrVal, -1, lpCurrentClsid, 256, NULL, NULL); #else lstrcpy(lpCurrentClsid, clsidVar.bstrVal); #endif /* Comparaison avec l'entrée utilisateur */ if (! lstrcmp(lpCurrentClsid, lpClsid)) { bResult = TRUE; bContinue = FALSE; } VariantClear(&clsidVar); lpComClass->lpVtbl->Release(lpComClass); break; case WBEM_S_FALSE: bContinue = FALSE; break; default: MessageBox(hWnd, _T("Error during enumeration"), NULL, MB_OK | MB_ICONERROR); goto free_enum; } } while (bContinue); if (! bResult) MessageBox(hWnd, _T("No class found with this CLSID"), NULL, MB_OK | MB_ICONERROR); free_enum: lpEnum->lpVtbl->Release(lpEnum); free_request: SysFreeString(lpRequest); SysFreeString(lpWQL); release_services: lpServices->lpVtbl->Release(lpServices); free_connection_string: SysFreeString(lpConnectionString); lpLocator->lpVtbl->Release(lpLocator); the_end: return bResult; } /**
  • Instancie l'objet COM et analyse une éventuelle erreur
  • /
BOOL __stdcall CheckWithCreation(TCHAR *lpClsid, HWND hWnd) { CLSID clsid; /* CLSID de la classe, sous forme numérique */ HRESULT nReturnValue; /* Pour tester le retour de CoCreateInstance */ IUnknown *lpUnknown; /* Pointeur sur l'interface IUnknown */ BOOL bResult; bResult = FALSE; if (! GetClsidFromString(lpClsid, &clsid, hWnd)) goto the_end; nReturnValue = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, &IID_IUnknown, (void**)&lpUnknown); switch (nReturnValue) { case S_OK: lpUnknown->lpVtbl->Release(lpUnknown); bResult = TRUE; break; case REGDB_E_CLASSNOTREG: MessageBox(hWnd, _T("Registration error, or CLSTX unavailable"), NULL, MB_OK | MB_ICONERROR); break; case E_NOINTERFACE: MessageBox(hWnd, _T("This interface is not available"), NULL, MB_OK | MB_ICONERROR); break; default: MessageBox(hWnd, _T("Unknown error in CoCreateInstance"), NULL, MB_OK | MB_ICONERROR); break; } the_end: return bResult; } /**
  • Renvoie un CLSID à partir d'une chaîne représentant un CLSID
  • /
BOOL __stdcall GetClsidFromString(TCHAR* lpClsidStr, CLSID* lpClsid, HWND hWnd) { WCHAR lpClsidStrW[256]; /* lpClsidStr en unicode pour CLSIDFromString */ HRESULT nReturnValue; /* Récupération du code de retour */ BOOL bResult; bResult = FALSE; /* Passage en unicode si nécessaire */ #ifndef UNICODE MultiByteToWideChar(CP_ACP, 0, lpClsidStr, -1, lpClsidStrW, 256); #else lstrcpy(lpClsidStrW, lpClsidStr); #endif /* Conversion */ nReturnValue = CLSIDFromString(lpClsidStrW, lpClsid); switch (nReturnValue) { case CO_E_CLASSSTRING: MessageBox(hWnd, _T("CLSID improperly formatted"), NULL, MB_OK | MB_ICONERROR); break; case REGDB_E_CLASSNOTREG: MessageBox(hWnd, _T("Corresponding CLSID not found"), NULL, MB_OK | MB_ICONERROR); break; case REGDB_E_READREGDB: MessageBox(hWnd, _T("Cannot read registery"), NULL, MB_OK | MB_ICONERROR); break; case NOERROR: bResult = TRUE; break; default: MessageBox(hWnd, _T("Unknown error in CLSIDFromString"), NULL, MB_OK | MB_ICONERROR); } return bResult; }

Conclusion :


Appli C unicode ou non (Flag dans tools.h).
Windows 2000 pro minimum (Voir XP minimum).
Testée sous XP.
Pas de CRT (Exécutable de 16Ko).

Réalisé sous VC6. Non compatible Code::Blocks/MinGW.
En effet, pour WMI, il faut Wbemcli.h/Wbemidl.h, qui ne sont proposés avec MinGW.
Ces headers ne sont apparemment pas non plus fournis avec VC6... Il faut récupérer le WMI SDK dans le platform SDK.
Le dernier platform sdk supportant officiellement VC6 est récupérable ici :
http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.