Pratiques de codage COM
Cette rubrique explique comment rendre votre code COM plus efficace et plus robuste.
- l’opérateur __uuidof
- Le macro IID_PPV_ARGS
- le modèle SafeRelease
- pointeurs intelligents COM
Opérateur __uuidof
Lorsque vous générez votre programme, vous pouvez obtenir des erreurs d’éditeur de liens similaires à ce qui suit :
unresolved external symbol "struct _GUID const IID_IDrawable"
Cette erreur signifie qu’une constante GUID a été déclarée avec une liaison externe (extern), et que l’éditeur de liens n’a pas pu trouver la définition de la constante. La valeur d’une constante GUID est généralement exportée à partir d’un fichier de bibliothèque statique. Si vous utilisez Microsoft Visual C++, vous pouvez éviter d’avoir à lier une bibliothèque statique à l’aide de l’opérateur __uuidof. Cet opérateur est une extension de langage Microsoft. Elle retourne une valeur GUID à partir d’une expression. L’expression peut être un nom de type d’interface, un nom de classe ou un pointeur d’interface. À l’aide de __uuidof, vous pouvez créer l’objet Common Item Dialog comme suit :
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
__uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));
Le compilateur extrait la valeur GUID de l’en-tête, donc aucune exportation de bibliothèque n’est nécessaire.
Note
La valeur GUID est associée au nom de type en déclarant __declspec(uuid( ... ))
dans l’en-tête. Pour plus d’informations, consultez la documentation relative à __declspec dans la documentation Visual C++.
The IID_PPV_ARGS Macro
Nous avons vu que CoCreateInstance et QueryInterface exiger la coécution du paramètre final vers un type void**. Cela crée le potentiel d’une incompatibilité de type. Considérez le fragment de code suivant :
// Wrong!
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(
__uuidof(FileOpenDialog),
NULL,
CLSCTX_ALL,
__uuidof(IFileDialogCustomize), // The IID does not match the pointer type!
reinterpret_cast<void**>(&pFileOpen) // Coerce to void**.
);
Ce code demande l’interface IFileDialogCustomize, mais passe un pointeur IFileOpenDialog. L’expression reinterpret_cast contourne le système de type C++, de sorte que le compilateur ne intercepte pas cette erreur. Dans le meilleur cas, si l’objet n’implémente pas l’interface demandée, l’appel échoue simplement. Dans le pire des cas, la fonction réussit et vous avez un pointeur incompatible. En d’autres termes, le type de pointeur ne correspond pas à la table virtuelle réelle en mémoire. Comme vous pouvez l’imaginer, rien de bon ne peut se produire à ce stade.
Note
Un vtable (table de méthodes virtuelles) est une table de pointeurs de fonction. La table virtuelle est la façon dont COM lie un appel de méthode à son implémentation au moment de l’exécution. Les tables virtuelles ne sont pas coïncidentes : la plupart des compilateurs C++ implémentent des méthodes virtuelles.
La macro IID_PPV_ARGS permet d’éviter cette classe d’erreur. Pour utiliser cette macro, remplacez le code suivant :
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
avec ceci :
IID_PPV_ARGS(&pFileOpen)
La macro insère automatiquement __uuidof(IFileOpenDialog)
pour l’identificateur d’interface. Elle est donc garantie de correspondre au type de pointeur. Voici le code modifié (et correct) :
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
Vous pouvez utiliser la même macro avec QueryInterface:
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
Modèle SafeRelease
Le comptage de références est l’une de ces choses dans la programmation qui est essentiellement facile, mais aussi fastidieuse, ce qui facilite l’obtention de tort. Les erreurs classiques sont les suivantes :
- Échec de la publication d’un pointeur d’interface lorsque vous avez terminé de l’utiliser. Cette classe de bogue entraîne la fuite de mémoire et d’autres ressources par votre programme, car les objets ne sont pas détruits.
- Appel de mise en production avec un pointeur non valide. Par exemple, cette erreur peut se produire si l’objet n’a jamais été créé. Cette catégorie de bogue entraîne probablement un blocage de votre programme.
- Désréférencement d’un pointeur d’interface après mise en production est appelée. Ce bogue peut entraîner le blocage de votre programme. Pire, il peut entraîner un blocage de votre programme au hasard plus tard, ce qui rend difficile le suivi de l’erreur d’origine.
Une façon d’éviter ces bogues consiste à appeler mise en production via une fonction qui libère en toute sécurité le pointeur. Le code suivant montre une fonction qui effectue cette opération :
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Cette fonction prend un pointeur d’interface COM comme paramètre et effectue les opérations suivantes :
- Vérifie si le pointeur est NULL .
- Appelle mise en production si le pointeur n’est pas NULL.
- Définit le pointeur sur NULL .
Voici un exemple d’utilisation de SafeRelease
:
void UseSafeRelease()
{
IFileOpenDialog *pFileOpen = NULL;
HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
if (SUCCEEDED(hr))
{
// Use the object.
}
SafeRelease(&pFileOpen);
}
Si CoCreateInstance réussit, l’appel à SafeRelease
libère le pointeur. Si CoCreateInstance échoue, pFileOpen reste NULL. La fonction SafeRelease
recherche cette opération et ignore l’appel à Release .
Il est également sûr d’appeler SafeRelease
plusieurs fois sur le même pointeur, comme indiqué ici :
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
Pointeurs intelligents COM
La fonction SafeRelease
est utile, mais elle vous oblige à mémoriser deux choses :
- Initialisez chaque pointeur d’interface vers NULL .
- Appelez
SafeRelease
avant que chaque pointeur ne sorte de l’étendue.
En tant que programmeur C++, vous pensez probablement que vous n’avez pas à vous souvenir de l’une de ces choses. C’est pourquoi C++ a des constructeurs et des destructeurs. Il serait intéressant d’avoir une classe qui encapsule le pointeur d’interface sous-jacent et initialise et libère automatiquement le pointeur. En d’autres termes, nous voulons quelque chose comme ceci :
// Warning: This example is not complete.
template <class T>
class SmartPointer
{
T* ptr;
public:
SmartPointer(T *p) : ptr(p) { }
~SmartPointer()
{
if (ptr) { ptr->Release(); }
}
};
La définition de classe indiquée ici est incomplète et n’est pas utilisable comme indiqué. Au minimum, vous devez définir un constructeur de copie, un opérateur d’affectation et un moyen d’accéder au pointeur COM sous-jacent. Heureusement, vous n’avez pas besoin d’effectuer ce travail, car Microsoft Visual Studio fournit déjà une classe de pointeur intelligente dans le cadre de la bibliothèque de modèles actifs (ATL).
La classe de pointeur intelligent ATL est nommée CComPtr. (Il existe également une classe CComQIPtr, qui n’est pas abordée ici.) Voici l’exemple Ouvrir la boîte de dialogue réécrit pour utiliser CComPtr .
#include <windows.h>
#include <shobjidl.h>
#include <atlbase.h> // Contains the declaration of CComPtr.
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |
COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
CComPtr<IFileOpenDialog> pFileOpen;
// Create the FileOpenDialog object.
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
if (SUCCEEDED(hr))
{
// Show the Open dialog box.
hr = pFileOpen->Show(NULL);
// Get the file name from the dialog box.
if (SUCCEEDED(hr))
{
CComPtr<IShellItem> pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
// Display the file name to the user.
if (SUCCEEDED(hr))
{
MessageBox(NULL, pszFilePath, L"File Path", MB_OK);
CoTaskMemFree(pszFilePath);
}
}
// pItem goes out of scope.
}
// pFileOpen goes out of scope.
}
CoUninitialize();
}
return 0;
}
La principale différence entre ce code et l’exemple d’origine est que cette version n’appelle pas explicitement Release. Lorsque l’instance CComPtr sort de l’étendue, le destructeur appelle release sur le pointeur sous-jacent.
CComPtr est un modèle de classe. L’argument de modèle est le type d’interface COM. En interne, CComPtr contient un pointeur de ce type. CComPtr remplace l’opérateur >() et l’opérateur &() afin que la classe agisse comme le pointeur sous-jacent. Par exemple, le code suivant équivaut à appeler directement la méthode IFileOpenDialog ::Show :
hr = pFileOpen->Show(NULL);
CComPtr définit également une méthode CComPtr ::CoCreateInstance, qui appelle la fonction COM CoCreateInstance avec certaines valeurs de paramètre par défaut. Le seul paramètre obligatoire est l’identificateur de classe, comme l’illustre l’exemple suivant :
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
La méthode CComPtr ::CoCreateInstance est fournie uniquement comme commodité ; vous pouvez toujours appeler la fonction COM CoCreateInstance, si vous préférez.
Prochain
gestion des erreurs dans COM