Postupy kódování modelu COM
Toto téma popisuje způsoby, jak zajistit efektivnější a robustnější kód modelu COM.
- operátoru __uuidof
- makra IID_PPV_ARGS
- vzor SafeRelease
- inteligentních ukazatelů modelu COM
Operátor __uuidof
Při vytváření programu se můžou zobrazit chyby linkeru podobné následujícímu:
unresolved external symbol "struct _GUID const IID_IDrawable"
Tato chyba znamená, že byla deklarována konstanta GUID s externím propojením (extern) a linker nemohl najít definici konstanty. Hodnota konstanty GUID se obvykle exportuje ze souboru statické knihovny. Pokud používáte Microsoft Visual C++, můžete se vyhnout nutnosti propojit statickou knihovnu pomocí operátoru __uuidof. Tento operátor je rozšíření jazyka Microsoftu. Vrátí hodnotu GUID z výrazu. Výrazem může být název typu rozhraní, název třídy nebo ukazatel rozhraní. Pomocí __uuidofmůžete vytvořit objekt Dialogové okno Společné položky následujícím způsobem:
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
__uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));
Kompilátor extrahuje hodnotu GUID z hlavičky, takže není nutné exportovat žádnou knihovnu.
Poznámka
Hodnota GUID je přidružena k názvu typu deklarací __declspec(uuid( ... ))
v hlavičce. Další informace najdete v dokumentaci k __declspec v dokumentaci visual C++.
Makro IID_PPV_ARGS
Viděli jsme, že CoCreateInstance i QueryInterface vyžadují vynucení konečného parametru na typ void***. Tím se vytvoří potenciál neshody typů. Zvažte následující fragment kódu:
// 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**.
);
Tento kód žádá o rozhraní IFileDialogCustomize, ale předává IFileOpenDialog ukazatel. Výraz reinterpret_cast obchází systém typů C++, takže kompilátor tuto chybu nezachytí. V nejlepším případě, pokud objekt neimplementuje požadované rozhraní, volání jednoduše selže. V nejhorším případě bude funkce úspěšná a vy máte neshodný ukazatel. Jinými slovy, typ ukazatele neodpovídá skutečné virtuální tabulce v paměti. Jak si dokážete představit, v tomto okamžiku se nic dobrého nestane.
Poznámka
vtable (tabulka virtuálních metod) je tabulka ukazatelů na funkce. Vtable je způsob, jakým com vytvoří vazbu volání metody k její implementaci za běhu. Není náhodou, virtuální tabulky jsou způsob, jakým většina kompilátorů jazyka C++ implementuje virtuální metody.
Makro IID_PPV_ARGS pomáhá vyhnout se této třídě chyb. Chcete-li použít toto makro, nahraďte následující kód:
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
s tímto:
IID_PPV_ARGS(&pFileOpen)
Makro automaticky vloží __uuidof(IFileOpenDialog)
pro identifikátor rozhraní, takže je zaručeno, že odpovídá typu ukazatele. Tady je upravený (a správný) kód:
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
Stejné makro můžete použít s QueryInterface:
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
Model SafeRelease
Počítání odkazů je jednou z těchto věcí v programování, která je v podstatě jednoduchá, ale je také zdlouhavá, což usnadňuje špatně. Mezi typické chyby patří:
- Po dokončení používání se nedaří uvolnit ukazatel rozhraní. Tato třída chyby způsobí, že váš program nevrácí paměť a další prostředky, protože objekty nejsou zničeny.
- Volání release neplatným ukazatelem K této chybě může dojít například v případě, že se objekt nikdy nevytvořil. Tato kategorie chyby pravděpodobně způsobí chybové ukončení programu.
- Dereferencing ukazatele rozhraní po release je volána. Tato chyba může způsobit chybové ukončení programu. Horší je, že může dojít k chybovému ukončení programu náhodně později, což znesnadňuje sledování původní chyby.
Jedním ze způsobů, jak se těmto chybám vyhnout, je volání release prostřednictvím funkce, která bezpečně uvolní ukazatel. Následující kód ukazuje funkci, která to dělá:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Tato funkce přebírá ukazatel rozhraní MODELU COM jako parametr a provádí následující akce:
- Zkontroluje, zda je ukazatel null.
- Volá release, pokud ukazatel není null.
- Nastaví ukazatel na null.
Tady je příklad použití 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);
}
Pokud CoCreateInstance úspěšně, volání SafeRelease
uvolní ukazatel. Pokud coCreateInstance selže, pFileOpen zůstane NULL . Funkce SafeRelease
to zkontroluje a přeskočí volání release.
Je také bezpečné volat SafeRelease
více než jednou na stejném ukazateli, jak je znázorněno zde:
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
Inteligentní ukazatele modelu COM
Funkce SafeRelease
je užitečná, ale vyžaduje, abyste si vzpomněli na dvě věci:
- Inicializace každého ukazatele rozhraní na NULL.
- Volání
SafeRelease
před tím, než každý ukazatel přejde mimo rozsah
Jako programátor C++ si pravděpodobně myslíte, že byste si neměli pamatovat ani jednu z těchto věcí. Proto jazyk C++ má konstruktory a destruktory. Bylo by hezké mít třídu, která zabalí základní ukazatel rozhraní a automaticky inicializuje a uvolní ukazatel. Jinými slovy, chceme něco takového:
// Warning: This example is not complete.
template <class T>
class SmartPointer
{
T* ptr;
public:
SmartPointer(T *p) : ptr(p) { }
~SmartPointer()
{
if (ptr) { ptr->Release(); }
}
};
Definice třídy, která je zde uvedena, je neúplná a není použitelná, jak je znázorněno. Minimálně byste museli definovat konstruktor kopírování, operátor přiřazení a způsob, jak získat přístup k základnímu ukazateli modelu COM. Naštěstí nemusíte provádět žádnou z těchto prací, protože Microsoft Visual Studio již poskytuje třídu inteligentního ukazatele jako součást knihovny ATL (Active Template Library).
Třída inteligentního ukazatele ATL má název CComPtr. (Existuje také CComQIPtr třídy, která zde není popsána.) Zde je otevřít dialogové okno příklad přepsaný tak, aby používal 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;
}
Hlavní rozdíl mezi tímto kódem a původním příkladem je, že tato verze explicitně nevolá Release. Když CComPtr instance mimo rozsah, destruktor volá Release na podkladovém ukazateli.
CComPtr je šablona třídy. Argument šablony je typ rozhraní MODELU COM. Interně CComPtr drží ukazatel tohoto typu. CComPtr přepíše operátor >() a &() tak, aby třída vypadala jako základní ukazatel. Například následující kód je ekvivalentní volání IFileOpenDialog::Show metoda přímo:
hr = pFileOpen->Show(NULL);
CComPtr také definuje CComPtr::CoCreateInstance metoda, která volá funkci COM CoCreateInstance s některými výchozími hodnotami parametrů. Jediným povinným parametrem je identifikátor třídy, jak ukazuje následující příklad:
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
Metoda CComPtr::CoCreateInstance je poskytována čistě jako pohodlí; Pokud chcete, můžete stále volat funkci COM CoCreateInstance.
Další
Zpracování chyb v modelu COM