Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este tópico descreve maneiras de tornar seu código COM mais eficaz e robusto.
- O do Operador __uuidof
- O IID_PPV_ARGS Macro
- O padrão SafeRelease
- COM Smart Pointers
O operador __uuidof
Quando você cria seu programa, você pode obter erros de vinculador semelhantes aos seguintes:
unresolved external symbol "struct _GUID const IID_IDrawable"
Este erro significa que uma constante GUID foi declarada com ligação externa (externo), 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 o Microsoft Visual C++, você pode evitar a necessidade de vincular uma biblioteca estática usando o operador __uuidof. Este 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 é 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 tanto CoCreateInstance quanto QueryInterface exigem coagir o parâmetro final a um tipo de void**. Isso cria o potencial para uma incompatibilidade de tipo. Considere o seguinte fragmento de código:
// 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 o interface IFileDialogCustomize, mas passa um ponteiro IFileOpenDialog. A expressão reinterpret_cast contorna o sistema de tipo C++, portanto, o compilador não detetará 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 ao vtable real na memória. Como você pode imaginar, nada de bom pode acontecer nesse momento.
Observação
Um vtable (tabela de método virtual) é uma tabela de ponteiros de função. O vtable é como COM vincula uma chamada de método à sua implementação em tempo de execução. Não por acaso, vtables são como a maioria dos compiladores C++ implementam 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)
com isso:
IID_PPV_ARGS(&pFileOpen)
A macro insere automaticamente __uuidof(IFileOpenDialog) para o identificador de interface, por isso é 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 torna fácil errar. Os erros típicos incluem:
- Falha ao liberar um ponteiro de interface quando você terminar de usá-lo. Essa classe de bug fará com que seu programa vaze memória e outros recursos, porque os objetos não são destruídos.
- Chamar versão com um ponteiro inválido. Por exemplo, esse erro pode acontecer se o objeto nunca foi criado. Esta categoria de bug provavelmente fará com que o seu programa falhe.
- A desreferenciação de um ponteiro de interface após de lançamento é chamada. Este bug pode fazer com que o seu programa falhe. Pior, isso pode fazer com que seu programa falhe aleatoriamente mais tarde, tornando difícil rastrear o erro original.
Uma maneira de evitar esses bugs é chamar Release através de uma função que libera o ponteiro com segurança. 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;
}
}
Esta função usa um ponteiro de interface COM como parâmetro e faz o seguinte:
- Verifica se o ponteiro está NULL.
- Chama Release se o ponteiro não estiver 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-sucedida, a chamada para SafeRelease liberará o ponteiro. Se CoCreateInstance falhar, pFileOpen permanecerá NULL. A função SafeRelease verifica isso e ignora a chamada para Release.
Também é seguro chamar SafeRelease mais de uma vez no mesmo ponteiro, como mostrado aqui:
// Redundant, but OK.
SafeRelease(&pFileOpen);
SafeRelease(&pFileOpen);
Ponteiros inteligentes COM
A função SafeRelease é útil, mas requer que você se lembre de duas coisas:
- Inicialize cada ponteiro de interface para NULL .
- Chame
SafeReleaseantes 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 envolvesse o ponteiro da interface subjacente e inicializasse e liberasse automaticamente o ponteiro. Por 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 como 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 nada desse trabalho, porque o Microsoft Visual Studio já fornece uma classe de ponteiro inteligente como parte da ATL (Ative Template Library).
A classe de ponteiro inteligente ATL é denominada CComPtr. (Há também uma classe CComQIPtr, que não é discutida aqui.) Aqui está o exemplo de Caixa de Diálogo Abrir reescrito 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 principal diferença entre este código e o exemplo original é que esta versão não chama explicitamente Release. Quando a instância CComPtr sai do escopo, o destruidor chama Release no ponteiro subjacente.
CComPtr é um modelo de classe. O argumento template é o tipo de interface COM. Internamente, CComPtr contém um ponteiro desse tipo. CComPtr substitui operador>() e operador&() para que a classe aja como o ponteiro subjacente. Por exemplo, o código a seguir é equivalente a chamar o IFileOpenDialog::Show método diretamente:
hr = pFileOpen->Show(NULL);
CComPtr também define um método CComPtr::CoCreateInstance, que chama a função deCOMCoCreateInstance com alguns valores de parâmetro padrão. O único parâmetro necessário é o identificador de classe, como mostra o exemplo a seguir:
hr = pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
O método CComPtr::CoCreateInstance é fornecido puramente como uma conveniência; você ainda pode chamar a função COM CoCreateInstance, se preferir.
Seguinte