Rodrigo Strauss :: Blog
Resolvendo o bug usando CComPtr
Eu resolvi o bug de diversas maneiras até agora. Eu mudei o código para contorná-lo, criei um smart pointer feito em casa e usei o boost::shared_ptr (melhor solução até agora). Agora eu proponho mais do que uma solução, proponho um re-arquitetura: transformar todos os objetos em objetos COM. Isso resolve nosso problema, porque em COM todos os objetos são acessados através de ponteiros para interfaces, e as regras para gerenciamento de tempo de vida desses ponteiros são bem claras. Esses objetos, apesar de serem "objetos COM", não estão disponíveis para a runtime do COM como de costume, são todos objetos privados. Futuramente, poderemos facilmente exportar esses componentes em uma DLL, para que eles sejam acessíveis através de outras ferramentas que suportam COM, como VB, Delphi ou mesmo .NET.
Nesse exemplo, eu segui as regras básicas do COM: todas as funcionalidades são acessíveis através de interfaces, todos os objetos herdam e implementam IUnknown e o tempo de vida de um objeto é gerenciado por chamadas a IUnknown->AddRef() e IUnknown->Release(). Para implementar IUnknown e gerenciar o tempo de vida dos objetos eu optei por usar ATL, por tornar a implementação bem mais simples.
Vamos ao código, ele vale muito:
#define UNICODE #define _UNICODE // "descomente" isso se usar um projeto do VC com "precompiled header" // #include "stdafx.h" // // só precisamos disso para usar ATL // #include <atlbase.h> ATL::CComModule _Module; #include <atlcom.h> // // isso evita erros na conversão de ponteiros COM // eu expliquei isso em ../../../content.1bit/weblog/cpp_comma_op // #define IC(pp) (static_cast<IUnknown*>(*pp),(void**)pp) // // Essa será nossa interface. Antes que você pergunte, // SIM, C++ TEM SUPORTE A INTERFACES // é só definir todos os métodos como virtual e colocar um "=0" // no final para dizer que não é implementado // struct __declspec(uuid("D0C2F56E-704E-45ce-B6F8-7E9D0F7F8723")) ITest1 : public IUnknown // toda interface COM herda de IUnknown { virtual HRESULT get_dw(DWORD* pdw) =0; virtual HRESULT set_dw(DWORD dw) =0; virtual HRESULT set_bstrValue(BSTR str) =0; virtual HRESULT get_bstrValue(BSTR* pbstr) =0; }; class CTest1 : public CComObjectRootEx<CComSingleThreadModel>, // implementação ATL de IUnknown public ITest1 { private: DWORD m_dwValue; // // nossa string agora será um BSTR. Vamos usar CComBSTR para não // nos preocuparmos com gerenciamento de memória // CComBSTR m_bstrValue; public: CTest1() : m_dwValue(0) {} // // Para fazer um objeto COM o ATL só precisa que você herde de // CComObjectRootEx (ou similares) e coloque um mapa de interfaces // BEGIN_COM_MAP(CTest1) COM_INTERFACE_ENTRY(ITest1) END_COM_MAP() // // implementação de ITest1 // HRESULT get_dw(DWORD* pdw) { if(!pdw) return E_POINTER; *pdw = m_dwValue; return S_OK; } HRESULT set_dw(DWORD dw) { m_dwValue = dw; return S_OK; } HRESULT set_bstrValue(BSTR str) { m_bstrValue = str; return S_OK; } HRESULT get_bstrValue(BSTR* pbstr) { if(!pbstr) return E_POINTER; *pbstr = m_bstrValue.Copy(); return S_OK; } // // vamos facilitar nossa vida // typedef ATL::CComCreator<CComObject<CTest1> > Creator; }; // // Interface ITest2 // struct __declspec(uuid("7BA53C86-5B50-4b69-ACC4-652E60FE2FC9")) ITest2 : public IUnknown { virtual HRESULT Init(DWORD dw, BSTR str) =0; virtual HRESULT GetTest1(ITest1** ppTest1) =0; }; class CTest2 : public CComObjectRootEx<CComSingleThreadModel>, // implementação ATL de IUnknown public ITest2 { private: // // vamos usar o CComPtr para não nos preocuparmos com // gerenciamento de tempo de vida do objeto COM // CComPtr<ITest1> m_pTest1; public: BEGIN_COM_MAP(CTest2) COM_INTERFACE_ENTRY(ITest2) END_COM_MAP() // // Não preciso um destrutor para desalocar o ponteiro. // E nem precisei usar um linguagem mais limitada ou uma runtime lenta // // // implementação de ITest2 // HRESULT Init(DWORD dw, BSTR str) { HRESULT hr; hr = CTest1::Creator::CreateInstance(NULL, __uuidof(ITest1), IC(&m_pTest1)); if(FAILED(hr)) return hr; m_pTest1->set_dw(dw); m_pTest1->set_bstrValue(str); return S_OK; } HRESULT GetTest1(ITest1** ppTest1) { *ppTest1 = NULL; if(m_pTest1.p == NULL) return E_UNEXPECTED; return m_pTest1.CopyTo(ppTest1); } typedef ATL::CComCreator<CComObject<CTest2> > Creator; }; int main(int argc, char* argv[]) { CComPtr<ITest1> pTest1; CComPtr<ITest2> pTest2; CComBSTR bstr; DWORD dw; CTest1::Creator::CreateInstance(NULL, __uuidof(ITest1), IC(&pTest1)); CTest2::Creator::CreateInstance(NULL, __uuidof(ITest2), IC(&pTest2)); bstr = L"Oi mamãe, eu sou uma string"; dw = 10; pTest1->set_bstrValue(bstr); pTest1->set_dw(dw); pTest2->Init(dw, bstr); // // se você não fizer isso antes de reusar a variável para um parâmetro // OUT, o CComPtr vai disparar um ASSERT, pq isso criaria um leak. // Quando você atribui NULL à um CComPtr é o equivalente do VB // a "Set p = Nothing" (libera o objeto) // pTest1 = NULL; pTest2->GetTest1(&pTest1); // // o mesmo problema com o CComBSTR. Precisamos liberá-lo antes de reusar // para um parâmetro OUT. Ok, ainda tem que fazer algum gerenciamento de // memória manualmente. // bstr.Empty(); pTest1->get_bstrValue(&bstr); pTest1->get_dw(&dw); return 0; }
Para compilar esse projeto, não crie um projeto ATL no Visual C++, senão você terá uma DLL. Crie um projeto "Win32 Console Application", ou compile isso em linha de comando com o Visual C++ Toolkit.
Em 27/06/2005 15:00, por Rodrigo Strauss





Moral da história: ATL facilita e é enxuto. Torna uma tarefa aparentemente complexa em C++ (como implementar IUnknown e Agregation em MTA e STA) em algo quase tão simples quanto .NET.
Sem contar que pro usuário dessas classes a preocupação com o como foi implementado é nula. Prova disso é que ao ser exportado para linguagens de mais alto nível só existirão os métodos a serem chamados.