Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym temacie opisano sposoby zwiększenia skuteczności i niezawodności kodu COM.
- operatora __uuidof
- makra IID_PPV_ARGS
- wzorca SafeRelease
- inteligentnych wskaźników COM
The __uuidof Operator
Podczas kompilowania programu mogą wystąpić błędy konsolidatora podobne do następujących:
unresolved external symbol "struct _GUID const IID_IDrawable"
Ten błąd oznacza, że stała GUID została zadeklarowana za pomocą powiązania zewnętrznego (extern), a konsolidator nie mógł odnaleźć definicji stałej. Wartość stałej identyfikatora GUID jest zwykle eksportowana z pliku biblioteki statycznej. Jeśli używasz języka Microsoft Visual C++, możesz uniknąć konieczności łączenia biblioteki statycznej przy użyciu operatora __uuidof. Ten operator jest rozszerzeniem języka firmy Microsoft. Zwraca wartość identyfikatora GUID z wyrażenia. Wyrażenie może być nazwą typu interfejsu, nazwą klasy lub wskaźnikiem interfejsu. Za pomocą __uuidofmożna utworzyć obiekt okna dialogowego wspólnego elementu w następujący sposób:
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
__uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));
Kompilator wyodrębnia wartość identyfikatora GUID z nagłówka, więc żaden eksport biblioteki nie jest konieczny.
Nuta
Wartość identyfikatora GUID jest skojarzona z nazwą typu, deklarując __declspec(uuid( ... )) w nagłówku. Aby uzyskać więcej informacji, zobacz dokumentację dotyczącą __declspec w dokumentacji języka Visual C++.
Makro IID_PPV_ARGS
Zobaczyliśmy, że zarówno CoCreateInstance, jak i QueryInterface wymagają wymuszania ostatecznego parametru do void** typu. Spowoduje to utworzenie potencjalnego niezgodności typów. Rozważmy następujący fragment kodu:
// 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**.
);
Ten kod prosi o interfejs IFileDialogCustomize, ale przekazuje wskaźnik IFileOpenDialog. Wyrażenie reinterpret_cast pomija system typów języka C++, więc kompilator nie przechwyci tego błędu. W najlepszym przypadku, jeśli obiekt nie implementuje żądanego interfejsu, wywołanie po prostu kończy się niepowodzeniem. W najgorszym przypadku funkcja powiedzie się i masz niedopasowany wskaźnik. Innymi słowy, typ wskaźnika nie jest zgodny z rzeczywistą tabelą wirtualną w pamięci. Jak można sobie wyobrazić, nic dobrego nie może się zdarzyć w tym momencie.
Nuta
vtable (tabela metod wirtualnych) to tabela wskaźników funkcji. Tabela wirtualna to sposób, w jaki com wiąże wywołanie metody z implementacją w czasie wykonywania. Nie przypadkowo, tabele wirtualne są sposobem, w jaki większość kompilatorów języka C++ implementuje metody wirtualne.
Makro IID_PPV_ARGS pomaga uniknąć tej klasy błędów. Aby użyć tego makra, zastąp następujący kod:
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
w następujący sposób:
IID_PPV_ARGS(&pFileOpen)
Makro automatycznie wstawia __uuidof(IFileOpenDialog) dla identyfikatora interfejsu, dlatego ma gwarancję dopasowania do typu wskaźnika. Oto zmodyfikowany (i poprawny) kod:
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
Tego samego makra można użyć z QueryInterface:
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
Wzorzec saferelease
Liczenie odwołań jest jedną z tych rzeczy w programowaniu, które jest w zasadzie łatwe, ale jest również żmudne, co ułatwia wykroczenie. Typowe błędy obejmują:
- Nie można zwolnić wskaźnika interfejsu po zakończeniu korzystania z niego. Ta klasa usterki spowoduje, że program będzie wyciekać pamięci i innych zasobów, ponieważ obiekty nie są niszczone.
- Wywoływanie release z nieprawidłowym wskaźnikiem. Na przykład ten błąd może wystąpić, jeśli obiekt nigdy nie został utworzony. Ta kategoria usterki prawdopodobnie spowoduje awarię programu.
- Wyłuszczenie wskaźnika interfejsu po wywołaniu Release. Ta usterka może spowodować awarię programu. Co gorsza, może to spowodować awarię programu w losowym późniejszym czasie, co utrudnia śledzenie oryginalnego błędu.
Jednym ze sposobów uniknięcia tych błędów jest wywołanie release za pośrednictwem funkcji, która bezpiecznie zwalnia wskaźnik. Poniższy kod przedstawia funkcję, która wykonuje następujące czynności:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Ta funkcja przyjmuje wskaźnik interfejsu COM jako parametr i wykonuje następujące czynności:
- Sprawdza, czy wskaźnik ma wartość null.
- Wywołuje release, jeśli wskaźnik nie jest null.
- Ustawia wskaźnik na wartość null.
Oto przykład użycia 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);
}
Jeśli CoCreateInstance powiedzie się, wywołanie SafeRelease zwalnia wskaźnik. Jeśli CoCreateInstance zakończy się niepowodzeniem, pFileOpen pozostanie null. Funkcja SafeRelease sprawdza tę funkcję i pomija wywołanie Release.
Można również bezpiecznie wywołać SafeRelease więcej niż raz w tym samym wskaźniku, jak pokazano poniżej:
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
Wskaźniki inteligentne COM
Funkcja SafeRelease jest przydatna, ale wymaga zapamiętania dwóch rzeczy:
- Zainicjuj każdy wskaźnik interfejsu, aby wartość NULL.
- Wywołaj
SafeRelease, zanim każdy wskaźnik wykracza poza zakres.
Jako programista języka C++ prawdopodobnie myślisz, że nie musisz pamiętać żadnej z tych rzeczy. W końcu dlatego język C++ ma konstruktory i destruktory. Dobrze byłoby mieć klasę, która opakowuje bazowy wskaźnik interfejsu i automatycznie inicjuje i zwalnia wskaźnik. Innymi słowy, chcemy czegoś podobnego do następującego:
// Warning: This example is not complete.
template <class T>
class SmartPointer
{
T* ptr;
public:
SmartPointer(T *p) : ptr(p) { }
~SmartPointer()
{
if (ptr) { ptr->Release(); }
}
};
Definicja klasy pokazana w tym miejscu jest niekompletna i nie można jej używać, jak pokazano poniżej. Co najmniej należy zdefiniować konstruktor kopiujący, operator przypisania i sposób uzyskiwania dostępu do bazowego wskaźnika COM. Na szczęście nie musisz wykonywać żadnej z tych czynności, ponieważ program Microsoft Visual Studio udostępnia już inteligentną klasę wskaźnika w ramach biblioteki Active Template Library (ATL).
Klasa inteligentnego wskaźnika ATL nosi nazwę CComPtr. (Istnieje również klasa CComQIPtr, która nie jest tutaj omawiana). Oto Otwórz okno dialogowe przykład przepisany do użycia 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;
}
Główną różnicą między tym kodem a oryginalnym przykładem jest to, że ta wersja nie wywołuje jawnie Release. Gdy wystąpienie CComPtr wykracza poza zakres, destruktor wywołuje release na podstawowym wskaźniku.
CComPtr jest szablonem klasy. Argument szablonu jest typem interfejsu COM. Wewnętrznie CComPtr zawiera wskaźnik tego typu. CComPtr zastępuje operator >() i operator &(), aby klasa działa jak wskaźnik bazowy. Na przykład następujący kod jest odpowiednikiem wywoływania metody IFileOpenDialog::Pokaż metodę bezpośrednio:
hr = pFileOpen->Show(NULL);
CComPtr definiuje również metodę CComPtr::CoCreateInstance, która wywołuje funkcję COM CoCreateInstance z pewnymi domyślnymi wartościami parametrów. Jedynym wymaganym parametrem jest identyfikator klasy, jak pokazano w następnym przykładzie:
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
Metoda CComPtr::CoCreateInstance jest udostępniana wyłącznie jako wygoda; Nadal możesz wywołać funkcję com CoCreateInstance, jeśli wolisz.
Następny
obsługa błędów w COM