Procedure di codifica COM
In questo argomento vengono descritti i modi per rendere il codice COM più efficace e affidabile.
Operatore __uuidof
Quando si compila il programma, è possibile che vengano visualizzati errori del linker simili ai seguenti:
unresolved external symbol "struct _GUID const IID_IDrawable"
Questo errore indica che una costante GUID è stata dichiarata con collegamento esterno (extern) e il linker non è riuscito a trovare la definizione della costante. Il valore di una costante GUID viene in genere esportato da un file di libreria statica. Se si usa Microsoft Visual C++, è possibile evitare la necessità di collegare una libreria statica usando l'operatore __uuidof . Questo operatore è un'estensione del linguaggio Microsoft. Restituisce un valore GUID da un'espressione. L'espressione può essere un nome di tipo di interfaccia, un nome di classe o un puntatore all'interfaccia. Usando __uuidof, è possibile creare l'oggetto Common Item Dialog come indicato di seguito:
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
__uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));
Il compilatore estrae il valore GUID dall'intestazione, quindi non è necessaria alcuna esportazione della libreria.
Nota
Il valore GUID è associato al nome del tipo dichiarando __declspec(uuid( ... ))
nell'intestazione. Per altre informazioni, vedere la documentazione per __declspec nella documentazione di Visual C++.
The IID_PPV_ARGS Macro
È stato rilevato che Sia CoCreateInstance che QueryInterface richiedono la coercing del parametro finale in un tipo void**. In questo modo viene creata una potenziale mancata corrispondenza del tipo. Si consideri il frammento di codice riportato di seguito.
// 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**.
);
Questo codice richiede l'interfaccia IFileDialogCustomize, ma passa un puntatore IFileOpenDialog. L'espressione reinterpret_cast aggirare il sistema di tipi C++, pertanto il compilatore non intercetta questo errore. Nel migliore dei casi, se l'oggetto non implementa l'interfaccia richiesta, la chiamata ha semplicemente esito negativo. Nel peggiore dei casi, la funzione ha esito positivo e si dispone di un puntatore non corrispondente. In altre parole, il tipo di puntatore non corrisponde alla tabella virtuale effettiva in memoria. Come si può immaginare, nulla di buono può accadere a quel punto.
Nota
Una tabella vtable (tabella dei metodi virtuali) è una tabella di puntatori a funzione. La tabella virtuale è il modo in cui COM associa una chiamata di metodo alla relativa implementazione in fase di esecuzione. Non in modo casuale, le vtable sono il modo in cui la maggior parte dei compilatori C++ implementa metodi virtuali.
La macro IID_PPV_ARGS consente di evitare questa classe di errore. Per usare questa macro, sostituire il codice seguente:
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
con il seguente:
IID_PPV_ARGS(&pFileOpen)
La macro viene inserita __uuidof(IFileOpenDialog)
automaticamente per l'identificatore dell'interfaccia, pertanto è garantito che corrisponda al tipo di puntatore. Ecco il codice modificato (e corretto):
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
È possibile usare la stessa macro con QueryInterface:
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
Modello Cassaforte Release
Il conteggio dei riferimenti è uno di questi elementi nella programmazione che è fondamentalmente facile, ma è anche noioso, che rende facile sbagliare. Gli errori tipici comprendono:
- Non è possibile rilasciare un puntatore all'interfaccia al termine dell'uso. Questa classe di bug causerà la perdita di memoria e altre risorse del programma, perché gli oggetti non vengono eliminati definitivamente.
- Chiamata di Release con un puntatore non valido. Ad esempio, questo errore può verificarsi se l'oggetto non è mai stato creato. Questa categoria di bug causerà probabilmente l'arresto anomalo del programma.
- Dereferenziare un puntatore di interfaccia dopo la chiamata a Release. Questo bug potrebbe causare l'arresto anomalo del programma. Peggio, potrebbe causare l'arresto anomalo del programma in un secondo momento casuale, rendendo difficile tenere traccia dell'errore originale.
Un modo per evitare questi bug consiste nel chiamare Release tramite una funzione che rilascia in modo sicuro il puntatore. Il codice seguente illustra una funzione che esegue questa operazione:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Questa funzione accetta un puntatore all'interfaccia COM come parametro ed esegue le operazioni seguenti:
- Controlla se il puntatore è NULL.
- Chiama Release se il puntatore non è NULL.
- Imposta il puntatore su NULL.
Di seguito è riportato un esempio di come usare 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);
}
Se CoCreateInstance ha esito positivo, la chiamata a SafeRelease
rilascia il puntatore. Se CoCreateInstance ha esito negativo, pFileOpen rimane NULL. La SafeRelease
funzione controlla questa situazione e ignora la chiamata a Release.
È anche possibile chiamare SafeRelease
più volte sullo stesso puntatore, come illustrato di seguito:
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
Puntatori intelligenti COM
La SafeRelease
funzione è utile, ma richiede di ricordare due cose:
- Inizializzare ogni puntatore dell'interfaccia su NULL.
- Chiamare
SafeRelease
prima che ogni puntatore esula dall'ambito.
In qualità di programmatore C++, è probabile che non sia necessario ricordare una di queste cose. Dopo tutto, ecco perché C++ ha costruttori e distruttori. Sarebbe bello avere una classe che esegue il wrapping del puntatore dell'interfaccia sottostante e inizializza e rilascia automaticamente il puntatore. In altre parole, vogliamo un aspetto simile al seguente:
// 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 definizione della classe illustrata di seguito è incompleta e non è utilizzabile come illustrato. Come minimo, è necessario definire un costruttore di copia, un operatore di assegnazione e un modo per accedere al puntatore COM sottostante. Fortunatamente, non è necessario eseguire alcuna operazione, perché Microsoft Visual Studio fornisce già una classe puntatore intelligente come parte di Active Template Library (ATL).
La classe puntatore intelligente ATL è denominata CComPtr. (C'è anche un Classe CComQIPtr , che non è descritta qui. Ecco l'esempio apri finestra di dialogo riscritto per usare 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 differenza principale tra questo codice e l'esempio originale è che questa versione non chiama in modo esplicito Release. Quando l'istanza CComPtr esce dall'ambito, il distruttore chiama Release sul puntatore sottostante.
CComPtr è un modello di classe. L'argomento modello è il tipo di interfaccia COM. Internamente, CComPtr contiene un puntatore di quel tipo. CComPtr esegue l'override di operator->() e operator&() in modo che la classe agisca come il puntatore sottostante. Ad esempio, il codice seguente equivale a chiamare direttamente il metodo IFileOpenDialog::Show :
hr = pFileOpen->Show(NULL);
CComPtr definisce anche un metodo CComPtr::CoCreateInstance , che chiama la funzione COM CoCreateInstance con alcuni valori di parametro predefiniti. L'unico parametro obbligatorio è l'identificatore di classe, come illustrato nell'esempio seguente:
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
Il metodo CComPtr::CoCreateInstance viene fornito esclusivamente per praticità. Se si preferisce, è comunque possibile chiamare la funzione COM CoCreateInstance .
Avanti