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





acho que ainda não estou pronto pra isso vou ler um pouco mais de Deitel