logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog

follow us in feedly

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 | e-mail | 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 | e-mail | em 17/10/2011 | #
Até onde sei, morreu...
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
  ::::