logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog


Explicando a sopa de letrinhas da programação C/C++ para Windows: ATL

Outros posts dessa mesma série:   Win32     COM     MFC

ATL - Active Template Library - é uma biblioteca de templates C++ criada pela Microsoft para simplificar a programação de objetos COM/OLE/ActiveX em C++. Ela foi criada inicialmente pela equipe do Visual Basic para simplificar o desenvolvimento interno, já que o VB até a versão 6 é todo baseado em COM. Hoje em dia ela é distribuída junto com todas as versões do Visual C++. É uma biblioteca pouco intrusiva, implementada em camadas e que tem um overhead muito pequeno, por ser baseada em templates. É usada pela Microsoft internamente em seus produtos, como o Windows Explorer, Windows Movie Maker, MMC e vários outros (ao contrário da MFC, que é pouco usada dentro da Microsoft).

O desenvolvimento COM em C++, apesar de não ser muito complicado, é trabalhoso. Muitas interfaces que precisam ser implementadas contém muitas funções cujo código de implementação é o mesmo para todos os componentes, o que torna o trabalho chato e repetitivo. A implementação da interface IUnknown, por exemplo, é sempre a mesma: controle de referência e solicitação das interfaces suportadas.

Além de suportar a implementação de objetos COM, a ATL é uma biblioteca com diversas classes e templates que facilitam muito a programação Windows, como classes para acesso ao registro, comunicação HTTP e SMTP, criptografia, BASE64, acesso à arquivos, ACLs, listas e hashmaps, etc.

Como um trecho de código vale muito mais do que 186.112.794 palavras, veja como é implementado um objeto COM e seu Class Factory em C++ puro:

//
// interface do nosso objeto COM
//
__interface __declspec(uuid("977BF132-B6B6-4d70-88BD-C427A2724B48"))
ITest : IUnknown
{
   HRESULT WINAPI Method1(BSTR str, ULONG ul);
};

//
// Objeto que implementa a class ITest
//
class CTest : public ITest
{
   DWORD m_ref;
public:
   CTest()
   {
      m_ref = 0;
   }

   //
   // implementação de IUnknown
   //
   STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject)
   {
      if(InlineIsEqualUnknown(riid))
      {
         AddRef();
         *ppvObject = static_cast<IUnknown*>(this);
         return S_OK;
      }
      else if(InlineIsEqualGUID(riid, __uuidof(ITest)))
      {
         AddRef();
         *ppvObject = static_cast<ITest*>(this);
         return S_OK;
      }
      
      return E_NOINTERFACE;
   }

   STDMETHOD_(ULONG,AddRef)()
   {
      return ++m_ref;
   }

   STDMETHOD_(ULONG,Release)()
   {
      DWORD ref = --m_ref;

      if(ref == 0)
         delete this;

      return ref;
   }

   //
   // implementação de ITest
   //
   STDMETHOD(Method1)(BSTR str, ULONG ul)
   {
      MessageBoxW(NULL, str, L"", MB_ICONEXCLAMATION);
      return S_OK;
   }
};
//
// Class Factory para o nosso obejto
//
class CTestClassFactory : public IClassFactory
{
   DWORD m_ref;
public:
   CTestClassFactory()
   {
      m_ref = 0;
   }

   //
   // quando o objeto é registrado, a runtime do Microsoft COM 
   // chama a função GetClassObject exportada pela DLL do objeto.
   // como vamos fazer tudo na mão agora, vamos criar esse helper
   //
   static HRESULT CreateClassFactory(REFIID riid, void **ppv)
   {
      HRESULT hr;
      IUnknown* p;

      try
      {
         p = new CTestClassFactory();
      }
      catch(...)
      {
         return E_OUTOFMEMORY;
      }

      p->AddRef();

      hr = p->QueryInterface(riid, ppv);

      p->Release();

      return hr;
   }

   //
   // implementação do IClassFactory
   //
   STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObject)
   {
      HRESULT hr;
      IUnknown* pUnk;

      if(pUnkOuter)
         return CLASS_E_NOAGGREGATION;

      try
      {
         //
         // pelo padrão C++, se o new falha uma exceção é disparada
         //
         pUnk = new CTest();
      }
      catch(...)
      {
         return E_OUTOFMEMORY;
      }
      
      pUnk->AddRef();

      hr = pUnk->QueryInterface(riid, ppvObject);

      
      pUnk->Release();

      return hr;
   }
   STDMETHOD(LockServer)(BOOL fLock)
   {
      return S_OK;
   }

   //
   // implementação de IUnknown
   //
   STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject)
   {
      if(InlineIsEqualUnknown(riid))
      {
         AddRef();
         *ppvObject = this;
         return S_OK;
      }
      else if(InlineIsEqualGUID(riid, IID_IClassFactory))
      {
         AddRef();
         *ppvObject = static_cast<IClassFactory*>(this);
         return S_OK;
      }

      return E_NOINTERFACE;
   }

   STDMETHOD_(ULONG,AddRef)()
   {
      return ++m_ref;
   }

   STDMETHOD_(ULONG,Release)()
   {
      DWORD ref = --m_ref;

      if(ref == 0)
         delete this;

      return ref;
   }
};

//
// E no sétimo dia Deus disse: "int main"
//
int main()
{
   HRESULT hr;
   BSTR bstr;
   IClassFactory* pClassFactory;
   ITest* pTest;

   //
   // criando o class factory
   //
   hr = CTestClassFactory::CreateClassFactory(IID_IClassFactory, (void**)&pClassFactory);
   if(FAILED(hr))
      return hr;

   //
   // solicitando gentilmente para que ele crie um objeto daquele tipo
   //
   hr = pClassFactory->CreateInstance(NULL, __uuidof(ITest), (void**)&pTest);
   if(FAILED(hr))
   {
      pClassFactory->Release();
      return hr;
   }

   //
   // usando o objeto
   //
   bstr = SysAllocString(L"Uma string bem legal");
   
   hr = pTest->Method1(bstr, 20);
   
   SysFreeString(bstr);

   if(FAILED(hr))
   {
      pClassFactory->Release();
      pTest->Release();
      return hr;
   }

   //
   // liberar as interfaces
   //
   pClassFactory->Release();
   pTest->Release();


   return S_OK;
}

Lembre-se, a única coisa realmente útil para nós nesse código é a implementação de ITest::Method1. O resto é tudo suporte ao contador de referências que todo objeto COM deve ter. Com ATL, nossa implementação seria assim:

class ATL_NO_VTABLE CTest1 : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CTest1, &CLSID_Test1>,
  public ITest1
{
public:

   DECLARE_REGISTRY_RESOURCEID(IDR_TEST1)

   BEGIN_COM_MAP(CTest1)
       COM_INTERFACE_ENTRY(ITest1)
   END_COM_MAP()

public:

   STDMETHOD(Method1)(BSTR p1, DWORD p2)
   {
      MessageBoxW(NULL, str, L"", MB_ICONEXCLAMATION);
      return S_OK;
   }
};

int main()
{
   HRESULT hr;
   CComPtr<ITest1> pTest;
   
   hr = CTest1::CreateInstance(NULL, &pTest);
   if(FAILED(hr))
      return hr;

   hr = pTest->Method1(CComBSTR(L"Uma string muuuuito mais legal"), 150);
   if(FAILED(hr))
      return hr;

   //
   // "olhe mamãe, eu sei coletar meu próprio lixo"
   //

   return S_OK;
}

Explicação: STDMETHOD nada mais é do que uma macro que coloca o retorno da função com HRESULT (padrão de retorno de erros COM) e coloca o calling convention como __stdcall, o mesmo das APIs do Windows.

Salvamos algumas dezenas de linhas de código usando ATL, já que ele tem uma implementação para IUnknown (single threaded e multi threaded, a nossa é só single) e um Class Factory, além de toda a implementação para que o nosso objeto seja registrado e usado por qualquer cliente COM, seja VB6, .NET, Delphi, etc. Se estivessemos fazendo um servidor OLE, a quantidade de código boilerplate que deixaríamos de escrever seria da ordem de centenas de linhas. E com a elegância e leveza que só o ATL tem :-)


Em 25/10/2005 03:55, por Rodrigo Strauss


  
 
 
Comentários
Maluco beleza | website | em 08/03/2007 | #
acho que ainda não estou pronto pra isso vou ler um pouco mais de Deitel
JJ Batista | em 16/10/2011 | #
E o que você teria a dizer sobre ATL Server?
Rodrigo Strauss | website | em 17/10/2011 | #
Até onde sei, morreu...
rebarba rebarba
  ::::