Práticas de codificação COM
Este tópico descreve maneiras de tornar seu código COM mais eficaz e robusto.
O operador __uuidof
Ao criar seu programa, você poderá obter erros de vinculador semelhantes aos seguintes:
unresolved external symbol "struct _GUID const IID_IDrawable"
Esse erro significa que uma constante GUID foi declarada com vinculação externa (extern) e o vinculador não pôde encontrar a definição da constante. O valor de uma constante GUID geralmente é exportado de um arquivo de biblioteca estática. Se você estiver usando Microsoft Visual C++, poderá evitar a necessidade de vincular uma biblioteca estática usando o operador __uuidof. Esse operador é uma extensão de idioma da Microsoft. Ele retorna um valor GUID de uma expressão. A expressão pode ser um nome de tipo de interface, um nome de classe ou um ponteiro de interface. Usando __uuidof, você pode criar o objeto Common Item Dialog da seguinte maneira:
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
__uuidof(pFileOpen), reinterpret_cast<void**>(&pFileOpen));
O compilador extrai o valor guid do cabeçalho, portanto, nenhuma exportação de biblioteca é necessária.
Observação
O valor guid está associado ao nome do tipo declarando __declspec(uuid( ... ))
no cabeçalho . Para obter mais informações, consulte a documentação para __declspec na documentação do Visual C++.
A macro IID_PPV_ARGS
Vimos que CoCreateInstance e QueryInterface exigem coerção do parâmetro final para um tipo void** . Isso cria o potencial para uma incompatibilidade de tipo. Considere o fragmento de código a seguir:
// 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**.
);
Esse código solicita a interface IFileDialogCustomize , mas passa um ponteiro IFileOpenDialog . A expressão reinterpret_cast contorna o sistema de tipos C++, portanto, o compilador não capturará esse erro. Na melhor das hipóteses, se o objeto não implementar a interface solicitada, a chamada simplesmente falhará. Na pior das hipóteses, a função é bem-sucedida e você tem um ponteiro incompatível. Em outras palavras, o tipo de ponteiro não corresponde à vtable real na memória. Como você pode imaginar, nada de bom pode acontecer naquele momento.
Observação
Uma vtable (tabela de método virtual) é uma tabela de ponteiros de função. A vtable é como COM associa uma chamada de método à sua implementação em tempo de execução. Não coincidentemente, vtables são como a maioria dos compiladores C++ implementa métodos virtuais.
A macro IID_PPV_ARGS ajuda a evitar essa classe de erro. Para usar essa macro, substitua o seguinte código:
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
por este:
IID_PPV_ARGS(&pFileOpen)
A macro insere __uuidof(IFileOpenDialog)
automaticamente para o identificador de interface, portanto, é garantido que corresponda ao tipo de ponteiro. Aqui está o código modificado (e correto):
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
Você pode usar a mesma macro com QueryInterface:
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
O padrão SafeRelease
A contagem de referências é uma daquelas coisas na programação que é basicamente fácil, mas também é tediosa, o que facilita o erro. Os erros típicos incluem:
- Falha ao liberar um ponteiro de interface quando terminar de usá-lo. Essa classe de bug fará com que seu programa vaze memória e outros recursos, pois os objetos não são destruídos.
- Chamando Release com um ponteiro inválido. Por exemplo, esse erro poderá ocorrer se o objeto nunca tiver sido criado. Essa categoria de bug provavelmente fará com que seu programa falhe.
- Desreferenciando um ponteiro de interface após Release ser chamado. Esse bug pode causar falha no programa. Pior, isso pode fazer com que seu programa falhe aleatoriamente mais tarde, dificultando o rastreamento do erro original.
Uma maneira de evitar esses bugs é chamar Release por meio de uma função que libera com segurança o ponteiro. O código a seguir mostra uma função que faz isso:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Essa função usa um ponteiro de interface COM como um parâmetro e faz o seguinte:
- Verifica se o ponteiro é NULL.
- Chama Release se o ponteiro não for NULL.
- Define o ponteiro como NULL.
Aqui está um exemplo de como usar 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 for bem-sucedido, a chamada para SafeRelease
liberará o ponteiro. Se CoCreateInstance falhar, pFileOpen permanecerá NULL. A SafeRelease
função verifica isso e ignora a chamada para Release.
Também é seguro chamar SafeRelease
mais de uma vez no mesmo ponteiro, conforme mostrado aqui:
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
Ponteiros Inteligentes COM
A SafeRelease
função é útil, mas exige que você se lembre de duas coisas:
- Inicialize cada ponteiro de interface para NULL.
- Chame
SafeRelease
antes que cada ponteiro saia do escopo.
Como programador C++, você provavelmente está pensando que não deveria ter que se lembrar de nenhuma dessas coisas. Afinal, é por isso que o C++ tem construtores e destruidores. Seria bom ter uma classe que encapsula o ponteiro de interface subjacente e inicializa e libera automaticamente o ponteiro. Em outras palavras, queremos algo assim:
// Warning: This example is not complete.
template <class T>
class SmartPointer
{
T* ptr;
public:
SmartPointer(T *p) : ptr(p) { }
~SmartPointer()
{
if (ptr) { ptr->Release(); }
}
};
A definição de classe mostrada aqui está incompleta e não é utilizável conforme mostrado. No mínimo, você precisaria definir um construtor de cópia, um operador de atribuição e uma maneira de acessar o ponteiro COM subjacente. Felizmente, você não precisa fazer nenhum deste trabalho, pois o Microsoft Visual Studio já fornece uma classe de ponteiro inteligente como parte da ATL (Biblioteca de Modelos Ativos).
A classe de ponteiro inteligente ATL é chamada CComPtr. (Há também uma classe CComQIPtr , que não é discutida aqui.) Aqui está o exemplo Abrir Caixa de Diálogo reescrita para usar 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;
}
A main diferença entre esse código e o exemplo original é que essa versão não chama explicitamente Release. Quando a instância do CComPtr fica fora do escopo, o destruidor chama Release no ponteiro subjacente.
CComPtr é um modelo de classe. O argumento de modelo é o tipo de interface COM. Internamente, CComPtr contém um ponteiro desse tipo. CComPtr substitui operator->() e operator&() para que a classe atue como o ponteiro subjacente. Por exemplo, o código a seguir é equivalente a chamar o método IFileOpenDialog::Show diretamente:
hr = pFileOpen->Show(NULL);
CComPtr também define um método CComPtr::CoCreateInstance , que chama a função COM CoCreateInstance com alguns valores de parâmetro padrão. O único parâmetro necessário é o identificador de classe, como mostra o próximo exemplo:
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
O método CComPtr::CoCreateInstance é fornecido puramente como uma conveniência; você ainda pode chamar a função COCreateInstance COM, se preferir.
Avançar