logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog

follow us in feedly

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 http://www.1bit.com.br/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


  
 
 
Comentários
Wanderley Caloni Jr | website | e-mail | em 27/06/2005 | #
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.
Fabio Galuppo | em 13/07/2005 | #
Ok, se a idéia é futuramente exportar como componente COM. Se quiser apenas compartilhar as interfaces, acredito que a mesma solução pode ser feita puramente em C++. :) Os próprios "smart pointers" ( auto_ptr e o futuro C++0x shared_ptr ) do C++ poderiam ser aplicados. A propósito vc "postou" a solução boost::shared_ptr, por aqui?

Grande abraço
Fabio Galuppo | em 13/07/2005 | #
Ok vi o link agora :(
Algo a dizer?
Nome:


Site:


E-mail:


Escreva o número vinte e seis:


 Não mostre meu e-mail no site, não serve pra nada mesmo...

Comentário





Os comentários devem ser sobre assuntos relativos ao post, eu provavelmente apagarei comentários totalmente offtopic. Se quiser me enviar uma mensagem, use o formulário de contato. E não esqueça: isso é um site pessoal e eu me reservo o direito de apagar qualquer comentário ofensivo ou inapropriado.
rebarba rebarba
  ::::