algumas notas sobre com e dcompaginas.fe.up.pt/~nflores/dokuwiki/lib/exe/fetch.php?... ·...

24
Miguel Pimenta Monteiro Algumas notas sobre COM e DCOM

Upload: nguyenthu

Post on 19-Nov-2018

214 views

Category:

Documents


0 download

TRANSCRIPT

Miguel Pimenta Monteiro

Algumas notas sobre

COM e DCOM

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 1

1. Componentes COM e DCOM

1.1 História

A especificação COM (Component Object Model) surgiu em 1993, na sequência da evolução

de tecnologias que permitiam a integração de aplicações e a criação de documentos

compostos nos sistemas operativos Windows. Essas tecnologias mais antigas ficaram

conhecidas por DDE (Dynamic Data Exchange), que definiu protocos e serviços de

comunicação entre aplicações para a troca de dados, e OLE (Object Linking and Embedding) permitindo a criação de documentos por várias aplicações, activando a

responsável pela criação e edição de cada parte.

O COM surgiu como uma forma de partilha, a nível binário, de código previamente

desenvolvido e compilado, usando um paradigma orientado a objectos. Sendo uma

especificação de formato binário, permite uma independência das linguagens de

programação, desde que estas sejam capazes de gerar esse formato binário.

Paralelamente, e desde os anos 80, a tecnologia da invocação remota de procedimentos

tinha vindo a evoluir, culminando com uma especificação da Open Sofware Foundation

(OSF) designada por DCE (Distributed Computing Environment). Esta especificação,

muitas vezes designada simplesmente por RPC (Remote Procedure Call), permite a

comunicação entre aplicações a executar em computadores diferentes através da invocação

remota de rotinas, com a passagem de parâmetros e resultados. A OSF/DCE RPC foi

implementada já no Windows NT 3.1.

Combinando RPC com COM foi possível activar e invocar objectos COM a executar em

processos diferentes dos clientes (quer na mesma máquina, quer em máquinas diferentes),

dando origem à especificação DCOM (Distributed COM), formalizada em 1996.

1.2 Especificação

Os componentes COM e DCOM são instâncias de classes que implementam um conjunto de

métodos agrupados em interfaces. As interfaces constituem um contrato de utilização do

componente e uma vez definidas e publicadas são imutáveis.

Um cliente que activou um componente COM apenas obtém um apontador para uma das

suas interfaces, invocando os métodos respectivos através desse apontador.

Todas as interfaces têm de derivar de uma interface pré-definida de nome IUnknown (por

convenção os nomes das interfaces devem começar por I), cuja definição em C++

(linguagem que iremos utilizar) é:

#define interface struct

interface IUnknown {

virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) = 0;

virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;

virtual ULONG STDMETHODCALLTYPE Release(void) = 0;

}

Assim as interfaces são classes puramente abstractas, sem qualquer implementação, que

simplesmente declaram os métodos que a compõem. Geralmente todos os métodos devem

retornar um indicador de sucesso (ou um código de erro), que é representado por um valor

do tipo HRESULT (exceptuam-se os métodos AddRef() e Release() de IUnknown).

Todas as interfaces e classes que as implementam têm associado um identificador que deve

ser um valor universalmente único. Na especificação COM esses identificadores são

números de 128 bits conhecidos por GUIDs (globally unique identifiers). Os GUIDs são

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 2

gerados utilizando um algoritmo que tem como entrada o instante temporal de geração, um

estado interno mantido na máquina que executa a geração, e pelo menos o MAC address da

carta de rede da máquina (se não existir, é também gerado um valor aleatório

estatisticamente independente). Existe um utilitário para gerar os GUIDs, de nome

guidgen.exe, que permite copiar, em diversos formatos, os valores gerados. Pode ver-se na

figura que se segue a operação do guidgen.

Guidgen

Novas interfaces deverão sempre derivar de IUnknown (ou de outra que dela derive). Por

exemplo:

interface IDog : public IUnknown {

virtual HRESULT STDMETHODCALLTYPE Bark(void) = 0;

virtual HRESULT STDMETHODCALLTYPE Snore(void) = 0;

}

interface ICat : public IUnknown {

virtual HRESULT STDMETHODCALLTYPE Eat(void) = 0;

virtual HRESULT STDMETHODCALLTYPE IgnoreMaster(void) = 0;

}

Quando se escreve o código de uma classe que implementa uma ou mais interfaces,

simplesmente podemos derivá-la de todas essas interfaces, como se mostra a seguir.

class DogCat : public IDog, public ICat {

...

protected:

virtual ~DogCat(void);

public:

DogCat(void);

STDMETHODIMP QueryInterface(REFIID riid, void **ppv);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

STDMETHODIMP Bark(void);

...

}

Aproveitamos assim a herança múltipla que o C++ suporta.

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 3

Tendo um componente COM já desenvolvido este poderá ter a forma de uma DLL

(biblioteca de carregamento dinâmico em memória) ou de um ficheiro executável directo

(.exe). Poderá ser activado no mesmo processo do cliente (.dll), tomando o nome de in- -proc server, ou noutro processo (.exe, ou .dll com surrogate) na mesma máquina (local server) ou noutra (remote server).

A representação simbólica de um componente COM põe em destaque as suas interfaces,

com um especial relevo para a interface IUnknown, que todos os componentes suportam.

IUnknown

ICat

IDog

class DogCat

Pode ver-se na figura a representação da classe do exemplo anterior.

1.3 A interface IUnknown

A interface IUnknown contém os métodos QueryInterface(), AddRef() e Release(). O

método QueryInterface() permite, a partir de um apontador para qualquer interface de

um componente, obter um apontador para qualquer outra interface suportada pelo mesmo

componente. As interfaces de um componente constituem a sua identidade, e o endereço

das suas interfaces indicam se se trata, ou não, do mesmo componente. O método

QueryInterface() (presente em todas as interfaces, que têm de derivar de IUnknown) terá

de possuir as propriadades de:

reflexividade: pA->QI(A) == pA, invocar QueryInterface() partindo de um

apontador de uma dada interface, para essa interface, terá de produzir o mesmo

apontador;

simetria: pA->QI(B) produz pB e pB->QI(A) produz o mesmo pA de partida,

quaisquer que sejam A e B suportadas pelo componente;

transitividade: se pA->QI(B) produz pB e pB->QI(C) produz pC, então pA->QI(C)

produz o mesmo pC.

É o cliente que tem de fazer a gestão da utilização das interfaces COM. Sempre que se

obtém um apontador para uma interface, através de chamadas a QueryInterface() ou a

outras funções do middleware COM, automaticamente incrementa-se um contador de

referências para interfaces do objecto. Sempre que o cliente copia um apontador para

interface, para uma outra variável sua, deverá invocar o método AddRef(), que incrementa

e retorna o valor desse contador. Sempre que o cliente deixa de necessitar de uma

interface deverá invocar o método Release(), que decrementa e retorna o valor do

contador. Quando este contador atinge o valor 0, o objecto COM é destruído.

1.4 A linguagem de definição de interfaces (IDL)

O projecto de um novo componente COM deve começar pela definição das interfaces que irá

suportar. As interfaces deverão agrupar métodos que de alguma forma estão relacionados

e que, porventura, possam ser reutilizadas noutros componentes.

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 4

Para manter a independência das linguagens de programação, as interfaces deverão ser

definidas num ficheiro de texto à parte (.idl), utilizando uma linguagem própria.

As interfaces, os métodos, e seus parâmetros, podem ser caracterizados por atributos que

identificam bem as suas propriedades.

Assim, por exemplo, se necessitarmos de definir uma interface que contém um método com

dois parâmetros inteiros, passados por valor, e um terceiro, passado por referência, que

retorna um valor calculado pelo método, poderíamos escrever a seguinte definição (por

exemplo no ficheiro adder.idl):

// adder.idl

import "unknwn.idl";

[ object, uuid(10000001-0000-0000-0000-000000000001) ]

interface ISum : IUnknown

{

HRESULT Sum([in] int x, [in] int y, [out, retval] int* retval);

};

A definição da interface IUnknown existe já fornecida com o sistema no ficheiro unknwn.idl.

A nova interface que definimos, chamada ISum, tem os atributos object (que indica que vai

ser implementada por um componente COM) e uuid (que especifica o seu identificador

(GUID) único). O único método definido na interface chama-se Sum() e contém 2

parâmetros de entrada (atributo in) e um de saída (atributo out). O atributo retval

indica que, em linguagens e ambientes mais limitados (p. ex. VB), este deve ser o valor de

retorno do método.

A linguagem IDL permite a utilização de diversos tipos a usar como parâmetros dos

métodos, que podem ir para além daqueles suportados pelas linguagens de implementação

do componente. Esses tipos incluem diversos tamanhos de inteiros (byte, hyper, long,

int, short e small), reais (double e float), caracteres (char e wchar_t), booleanos,

estruturas, diversas formas de endereços (apontadores) e diversas formas de arrays. Inclui

também inúmeros atributos, para caracterizar bem o comportamento pretendido pelos

vários elementos descritos, e a possibilidade de definir, além de interfaces, tipos (typedef),

bibliotecas de tipos (type libraries), classes, etc.

A sintaxe da linguagem é descrita detalhadamente na documentação do MSDN (Microsoft Development Network) ou do Platform SDK.

1.5 O cliente COM

Supondo que um componente COM suportando a interface ISum, descrita atrás, está já

desenvolvido e devidamente instalado, escrever um cliente que activa e invoca os métodos

do componente é relativamente simples.

Dispondo do ficheiro .idl com a definição das interfaces e métodos (e seus identificadores) e

do identificador do componente, é possível gerar, para a linguagem C++, os ficheiros de

inclusão e o código, que representam a tradução das definições incluídas.

Usa-se para isso um compilador de IDL, que no caso do Windows se chama midl. O midl é

capaz de gerar esses ficheiros, entre outros, invocando simplesmente:

> midl /win32 adder.idl // para gerar código fonte de 32 bits

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 5

Os ficheiros que nos interessam para construir o cliente são: adder.h e adder_i.c

O código de um cliente simples que use um componente que implementa a interface ISum

poderá então ser:

// client.cpp

#include <stdio.h>

#include "adder.h" // Generated by MIDL

// {10000002-0000-0000-0000-000000000001}

const CLSID CLSID_Adder =

{0x10000002,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}};

void main(void)

{

printf("Client: Calling CoInitialize()\n");

HRESULT hr = CoInitialize(NULL);

if(FAILED(hr))

printf("CoInitialize() failed\n");

IUnknown* pUnknown = 0;

ISum* pSum = 0;

printf("Client: Calling CoCreateInstance()\n");

hr = CoCreateInstance(CLSID_Adder, NULL, CLSCTX_SERVER, IID_IUnknown,

(void**)&pUnknown);

if(FAILED(hr))

printf("CoCreateInstance failed\n");

printf("Client: Calling QueryInterface() for ISum on %p\n", pUnknown);

hr = pUnknown->QueryInterface(IID_ISum, (void**)&pSum);

if(FAILED(hr))

printf("IID_ISum not supported\n");

hr = pUnknown->Release();

printf("Client: Called pUnknown->Release() reference count = %d\n", hr);

int sum;

hr = pSum->Sum(2, 3, &sum);

if(SUCCEEDED(hr))

printf("Client: Called Sum(2, 3) = %d\n", sum);

hr = pSum->Release();

printf("Client: Called pSum->Release() reference count = %d\n", hr);

printf("Client: Calling CoUninitialize()\n");

CoUninitialize();

}

O ficheiro de inclusão adder.h contém as definições necessárias às chamadas do

middleware do COM, assim como as declarações dos tipos, interfaces, seus métodos, e seus

identificadores (IID_IUnknown e IID_ISum). A concretização de algumas dessas

declarações aparece no ficheiro adder_i.c. Com o ficheiro .idl mostrado atrás o

identificador do componente não é gerado, pelo que tem de ser explicitamente definido no

código do cliente (CLSID_Adder).

A utilização dos serviços COM requer uma inicialização prévia por parte do cliente

(CoInitialize()) e a indicação explícita do fim da utilização desses serviços

(CoUninitialize()). Pelo meio fica a activação de um objecto com identificador

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 6

CLSID_Adder e a obtenção do apontador para a interface IUnknown (garantidamente

implementada), através do serviço CoCreateInstance(), seguindo-se a obtenção do

apontador para a interface ISum e a invocação do método Sum(). Repare-se ainda nas

chamadas a Release(), quando uma determinada interface deixa de ser necessária.

A construção do cliente na linha de comando e usando o compilador de C++ da Microsoft

poderia ser:

> cl client.cpp adder_i.c ole32.lib // produz cliente.exe

A biblioteca ole32 contém grande parte do código do middleware do COM.

Se o componente estiver devidamente instalado, e implementar correctamente a interface

ISum, uma execução do cliente deveria produzir, por exemplo:

> client

Client: Calling CoInitialize()

Client: Calling CoCreateInstance()

Client: Calling QueryInterface() for ISum on 008E27C8

Client: Called pUnknown->Release() reference count = 1

Client: Calling Sum(2, 3) = 5

Client: Called pSum->Release() reference count = 0

Client: Calling CoUninitialize()

1.6 O componente COM

O código de um componente terá de conter obviamente a classe que implementa os métodos

das interfaces de onde deriva. Poderá até conter outras classes suportando outras

interfaces. Mas terá também de incluir o código capaz de construir os objectos dessas

classes. Esse código encontra-se nos métodos de uma outra classe (que também é uma

classe COM) genericamente designada por class factory. Para cada classe implementando

interfaces deverá estar associada uma class factory. As class factories implementam a

interface standard COM IClassFactory. Em termos da linguagem IDL a interface

IClassFactory poderia ser definida como:

interface IClassFactory : IUnknown

{

HRESULT CreateInstance([in, unique] IUnknown *pUnkOuter,

[in] REFIID riid, [out, iid_is(riid)] void **ppvObject);

HRESULT LockServer([in] BOOL fLock);

}

É o método CreateInstance() o responsável pela criação do objecto associado à class

factory, passando-se-lhe em riid uma referência para o identificador da interface

pretendida e obtendo-se em ppvObject o endereço dessa interface. O 1º parâmetro

(pUnkOuter) só deve ser utilizado se a criação do novo objecto se destinar a fazer parte, por

agregação, de um outro objecto COM que o contenha.

O método LockServer() serve para manter o código da classe COM em memória, mesmo

se no momento não existir nenhuma instância activa dessa classe. Para isso LockServer()

deverá ser invocado com parâmetro TRUE. A remoção dessa condição faz-

-se passando o parâmetro FALSE.

Para que um cliente consiga invocar o método LockServer() da class factory, terá primeiro

de conseguir um apontador para a sua interface IClassFactory. A única forma de o

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 7

conseguir é através do serviço do middleware COM designado por:

HRESULT CoGetClassObject (

REFCLSID rclsid, // identificador da classe COM

DWORD dwClsContext, // tipo de implementação, pode ser CLSCTX_SERVER

COSERVERINFO *pServerInfo, // informação sobre a máquina servidora, NULL: local

REFIID riid, // referência para o identificador da interface (IClassFactory)

void **ppv ); // retorno do apontador para a interface

As class factories, presentes num componente COM e associadas cada uma delas a uma

classe COM, também têm de ser construídas antes de se poder utilizar a sua interface

IClassFactory. É esse o objectivo de um dos pontos de entrada da DLL que implementa o

componente. Esse ponto de entrada é uma função exportada com o nome

DllGetClassObject() e que tem a seguinte definição:

HRESULT __stdcall DllGetClassObject (

REFCLSID clsid, // identificador da classe COM

REFIID iid, // identificador da interface pretendida na class factory (IClassFactory)

void** ppv ); // retorno do apontador para a interface

O serviço CoCreateInstance(), invocado no cliente, encarrega-se da chamada desta

função, e uma vez obtida a interface IClassFactory, constrói e obtém uma interface do

objecto COM pretendido.

A implementação da classe COM tem ainda de tomar nota do número de interfaces em uso

e destruir o objecto quando esse número descer a 0.

Por outro lado, quando o middleware COM pretende saber se pode ou não eliminar da

memória o código do objecto COM, invoca uma outra função exportada

(DllCanUnloadNow()) que deverá retornar S_OK em caso afirmativo. Uma DLL COM pode

ser eliminada da memória se não houver nenhuma instância das suas classes COM e se o

número de locks (executados através de LockServer()) for 0.

Tendo em conta todas estas considerações, uma possível implementação de um componente

suportando a interface ISum seria:

// adder.cpp

#include <stdio.h>

#include "adder.h" // Generated by MIDL

// {10000002-0000-0000-0000-000000000001}

const CLSID CLSID_Adder =

{0x10000002,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}};

long g_cComponents = 0;

long g_cServerLocks = 0;

class CAdder : public ISum {

public:

ULONG __stdcall AddRef(); // IUnknown

ULONG __stdcall Release();

HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

HRESULT __stdcall Sum(int x, int y, int* retval); // ISum

CAdder() : m_cRef(1) { g_cComponents++; }

~CAdder() { g_cComponents--; }

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 8

private:

ULONG m_cRef;

};

ULONG CAdder::AddRef() {

return ++m_cRef;

}

ULONG CAdder::Release() {

if (--m_cRef != 0)

return m_cRef;

delete this;

return 0;

}

HRESULT CAdder::QueryInterface(REFIID iid, void** ppv) {

if (iid == IID_IUnknown)

*ppv = (IUnknown*)this;

else if (iid == IID_ISum)

*ppv = (ISum*)this;

else {

*ppv = NULL;

return E_NOINTERFACE;

}

AddRef();

return S_OK;

}

HRESULT CAdder::Sum(int x, int y, int* retval) {

*retval = x + y;

return S_OK;

}

/***************************** class factory ******************************/

class CFactory : public IClassFactory {

public:

ULONG __stdcall AddRef(); //IUnknown

ULONG __stdcall Release();

HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, //IClassFactory

REFIID iid, void** ppv);

HRESULT __stdcall LockServer(BOOL bLock);

CFactory() : m_cRef(1) { }

~CFactory() { }

private:

ULONG m_cRef;

};

ULONG CFactory::AddRef() {

return ++m_cRef;

}

ULONG CFactory::Release() {

if (--m_cRef != 0)

return m_cRef;

delete this;

return 0;

}

HRESULT CFactory::QueryInterface(REFIID iid, void** ppv) {

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 9

if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))

*ppv = (IClassFactory *)this;

else {

*ppv = NULL;

return E_NOINTERFACE;

}

AddRef();

return S_OK;

}

HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, REFIID iid,

void** ppv) {

if (pUnknownOuter != NULL)

return CLASS_E_NOAGGREGATION; // this component does not support

// aggregation

CAdder *pAdder = new CAdder;

if (pAdder == NULL)

return E_OUTOFMEMORY;

HRESULT hr = pAdder->QueryInterface(iid, ppv);

pAdder->Release();

return hr;

}

HRESULT CFactory::LockServer(BOOL bLock) {

if (bLock)

g_cServerLocks++;

else

g_cServerLocks--;

return S_OK;

}

/************************** exported functions ****************************/

HRESULT __stdcall DllCanUnloadNow() {

if (g_cServerLocks == 0 && g_cComponents == 0)

return S_OK;

else

return S_FALSE;

}

HRESULT __stdcall DllGetClassObject(REFCLSID clsid, REFIID iid, void** ppv) {

if (clsid != CLSID_Adder)

return CLASS_E_CLASSNOTAVAILABLE;

CFactory* pFactory = new CFactory;

if (pFactory == NULL)

return E_OUTOFMEMORY;

HRESULT hr = pFactory->QueryInterface(iid, ppv);

pFactory->Release();

return hr;

}

Notar que a contagem de objectos CAdder e a contagem de locks é feita em duas variáveis

globais inicializadas em 0, e que a contagem de referências para interfaces é feita em

variáveis privadas (m_cRef) das classes que as implementam.

A compilação do código anterior necessita do ficheiro de inclusão adder.h e também do

ficheiro adder_i.c, gerados a partir do compilador de IDL, e ainda da indicação das

funções a exportar, o que pode ser feito com um ficheiro .def, como o que se mostra a

seguir:

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 10

; adder.def

LIBRARY adder.dll

EXPORTS

DllGetClassObject PRIVATE

DllCanUnloadNow PRIVATE

A invocação do compilador na linha de comando do Windows pode então ser:

> cl /LD adder.cpp adder_i.c adder.def // produz adder.dll

1.7 Registo do componente COM

Para que o componente construído atrás possa ser activado, falta ainda uma operação: o

seu registo numa base de dados por forma a que o middleware possa localizar o ficheiro

executável quando for necessário. No caso do Windows essa base de dados encontra-se na

registry sob a sub-árvore HKEY_CLASSES_ROOT.

O serviço CoCreateInstance() procura o identificador da classe sob a chave CLSID e o local

do ficheiro que implementa a classe na chave InprocServer32, no caso de uma DLL.

Essa informação pode ser introduzida na registry usando um ficheiro de texto que descreva

as chaves a criar, e o utilitário regedit.

Para o exemplo que temos vindo a seguir esse ficheiro poderia ser (adder.reg):

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{10000002-0000-0000-0000-000000000001}]

@="Adder Sample"

[HKEY_CLASSES_ROOT\CLSID\{10000002-0000-0000-0000-000000000001}\InprocServer32]

@="C:\\dir\\adder.dll"

É também possível incluir no componente o código necessário para efectuar o seu próprio

registo (e também para eliminar esse registo). Esse código deverá ser colocado em duas

outras funções exportadas (numa DLL) com os nomes DllRegisterServer() e

DllUnregisterServer(). Estas funções podem ser invocadas usando o programa utilitário

regsvr32 (sem opções para o registo, e com a opção /u para eliminar o registo).

Como exemplo, o código necessário para a escrita das duas entradas anteriores na registry

poderia ser:

#include <olectl.h> // definição de alguns símbolos

HINSTANCE g_hinst; // variável global

...

// main entry point

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, void* pv) {

g_hinst = hInstance;

return TRUE;

}

...

const char *g_rtable[][3] = { // formato: { chave, nome, valor }

{ “CLSID\\{10000002-0000-0000-0000-000000000001}”, 0, “Adder Sample” },

{ “CLSID\\{10000002-0000-0000-0000-000000000001}\\InprocServer32”, 0,

(const char *) -1 } // valor de marcação inválido

};

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 11

HRESULT __stdcall DllUnregisterServer() { // DllRegisterServer uses this

HRESULT hr = S_OK;

int n_entries = sizeof(g_rtable)/sizeof(*g_rtable);

for (int k= n_entries-1; k>=0; k--) {

const char *keyname = g_rtable[k][0];

long err = RegDeleteKeyA(HKEY_CLASSES_ROOT, keyname);

if (err != ERROR_SUCCESS)

hr = S_FALSE;

}

return hr;

}

HRESULT __stdcall DllRegisterServer() {

HRESULT hr = S_OK;

char serverfile[MAX_PATH];

GetModuleFileNameA(g_hinst, serverfile, MAX_PATH); // component pathname

int n_entries = sizeof(g_rtable)/sizeof(*g_rtable);

for (int k=0; SUCCEEDED(hr) && k < n_entries; k++) {

const char *keyname = g_rtable[k][0];

const char *valuename = g_rtable[k][1];

const char *value = g_rtable[k][2];

if (value == (const char *) -1)

value = serverfile;

HKEY hkey;

long err = RegCreateKeyA(HKEY_CLASSES_ROOT, keyname, &hkey);

if (err == ERROR_SUCCESS) {

err = RegSetValueExA(hkey, valuename, 0, REG_SZ,

(const BYTE *) value, strlen(value)+1);

RegCloseKey(hkey);

}

if (err != ERROR_SUCCESS) {

DllUnregisterServer();

hr = SELFREG_E_CLASS; // defined in olectl.h

}

}

return hr;

}

É de notar que a eliminação das chaves da registry se faz por ordem inversa, uma vez que

estas só se podem apagar se não contiverem sub-chaves. Assim, é necessário ter isso em

atenção quando se escreve a tabela g_rtable[][].

Estas duas funções têm de ser exportadas da DLL, para que o utilitário regsvr32 as

consiga invocar. Logo, quando da construção da DLL estes nomes devem figurar no

ficheiro .def:

; adder.def

LIBRARY adder.dll

EXPORTS

DllGetClassObject PRIVATE

DllCanUnloadNow PRIVATE

DllRegisterServer PRIVATE

DllUnregisterServer PRIVATE

Construído o componente, e feito o respectivo registo, deve agora ser possível executar o

cliente, na mesma máquina, sem qualquer problema.

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 12

2. Composição de componentes

É possível, a partir de componentes já desenvolvidos, construir outros, que implementem

as mesmas interfaces (e mais algumas), reutilizando-os. No entanto, o novo componente

deve ter a sua própria identidade e respeitar todas as propriedades que o método

QueryInterface() deve ter.

São utilizadas, em geral, duas técnicas para fazer a composição de objectos COM:

contenção (containment) e agregação.

No método de contenção o componente que necessita de implementar interfaces já

disponibilizadas por outro (o contentor) deverá ele próprio activar esse componente (o

contido) e implementar todas as suas interfaces. No entanto, a implementação dos

métodos dessas interfaces são meras chamadas aos métodos do componente contido. Basta

para isso que o contentor mantenha internamente apontadores para as interfaces do

contido. Pode ver-se na figura seguinte um esquema desta técnica.

contido

IUnknown

ISum

IUnknown

ISum

IMultiplycontentor

O método da agregação pretende ser um pouco mais eficiente, expondo directamente as

interfaces do objecto agregado. Exige, no entanto, que este tenha sido desenvolvido com

suporte à agregação. Devido às regras de QueryInterface() é necessário que o objecto

agregado conheça pelo menos uma interface do agregante para poder retornar apontadores

para essas interfaces. Um esquema desta técnica pode ser visto a seguir.

agregado

IUnknown

IUnknown

ISum

IMultiplyagregante

INoAgUnknown

2.1 Contenção

Pretende-se desenvolver um componente que implemente duas interfaces: ISum e

IMultiply, usando o componente desenvolvido anteriormente para a interface ISum.

Usando a técnica da contenção, o novo componente terá de implementar as duas interfaces,

pelo que o seu ficheiro .idl (calculator.idl) terá de ser escrito da seguinte forma:

// calculator.idl

import "unknwn.idl";

[ object, uuid(10000001-0000-0000-0000-000000000001) ]

interface ISum : IUnknown

{

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 13

HRESULT Sum([in] int x, [in] int y, [out, retval] int* retval);

};

[ object, uuid(10000011-0000-0000-0000-000000000001) ]

interface IMultiply : IUnknown

{

HRESULT Multiply([in] int x, [in] int y, [out, retval] int* retval);

};

A implementação deste componente segue a que já foi apresentada para o componente

Adder, com as alterações que se mostram a seguir:

#include "calculator.h" // Generated by MIDL

const CLSID CLSID_Calculator =

{0x10000012,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}};

const CLSID CLSID_Adder =

{0x10000002,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}};

. . .

class CCalculator : public IMultiply, public ISum {

public:

ULONG __stdcall AddRef();

ULONG __stdcall Release();

HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

HRESULT __stdcall Sum(int x, int y, int* retval);

HRESULT __stdcall Multiply(int x, int y, int* retval);

HRESULT Init();

CCalculator();

~CCalculator();

private:

ULONG m_cRef;

ISum* m_pSum; // cache for interface ISum

};

CCalculator::CCalculator() : m_cRef(1), m_pSum(NULL) {

g_cComponents++;

}

CCalculator::~CCalculator() {

g_cComponents--;

m_pSum->Release();

}

// This code goes in an Init method because a constructor cannot return an error code

HRESULT CCalculator::Init() {

return CoCreateInstance(CLSID_Adder, NULL, CLSCTX_SERVER,

IID_ISum, (void**)&m_pSum);

}

HRESULT CCalculator::QueryInterface(REFIID riid, void** ppv) {

if(riid == IID_IUnknown)

*ppv = (IUnknown*)(IMultiply*)this;

else if(riid == IID_ISum)

*ppv = (ISum*)this;

else if(riid == IID_IMultiply)

*ppv = (IMultiply*)this;

else {

*ppv = NULL;

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 14

return E_NOINTERFACE;

}

AddRef();

return S_OK;

}

HRESULT CCalculator::Sum(int x, int y, int* retval) {

return m_pSum->Sum(x, y, retval); // call delegation

}

HRESULT CCalculator::Multiply(int x, int y, int* retval) {

*retval = x * y;

return S_OK;

}

. . .

HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, REFIID riid,

void** ppv) {

if(pUnknownOuter != NULL)

return CLASS_E_NOAGGREGATION;

CCalculator *pCalculator = new CCalculator();

if(pCalculator == NULL)

return E_OUTOFMEMORY;

HRESULT hr = pCalculator->Init();

if (FAILED(hr))

return hr;

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

pCalculator->Release();

return hr;

}

. . .

É de notar a existência de uma variável privada interna (m_pSum) na classe Calculator

que irá conter o endereço da interface ISum que é implementada no objecto contido. Esta

variável privada é inicializada quando da construção do objecto contentor (em

CFactory::CreateInstance()), chamando a sua função Init(). O destrutor faz o

Release() desta interface.

Quando o método Sum() for chamado pelo cliente final, executa-se o método Sum() do

contentor, que se limita a chamar o mesmo método do contido. A interface e o método

Multiply() são totalmente implementados pelo contentor.

2.2 Agregação

Se se pretender agora usar a técnica da agregação e pressupondo que o componente a

agregar foi desenvolvido com vista a suportá-la, a implementação do agregante não difere

muito do caso anterior. O agregante não implementa as interfaces suportadas pelo

agregado, mas tem de manter um apontador para a sua interface IUnknown. As diferenças

no código relativas à técnica da contenção são ilustradas a seguir:

. . .

class CCalculator : public IMultiply {

public:

ULONG __stdcall AddRef();

ULONG __stdcall Release();

HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

HRESULT __stdcall Multiply(int x, int y, int* retval);

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 15

HRESULT Init();

CCalculator();

~CCalculator();

private:

ULONG m_cRef;

IUnknown* m_pUnknownInner; // cache for aggregatee IUnknown

};

. . .

HRESULT CCalculator::Init() {

return CoCreateInstance(CLSID_Adder, (IUnknown*)this, CLSCTX_INPROC_SERVER,

IID_IUnknown, (void**)&m_pUnknownInner);

}

. . .

CCalculator::~CCalculator() {

g_cComponents--;

m_pUnknownInner->Release();

}

. . .

HRESULT CCalculator::QueryInterface(REFIID riid, void** ppv) {

if(riid == IID_IUnknown)

*ppv = (IUnknown*)this;

else if(riid == IID_ISum)

return m_pUnknownInner->QueryInterface(riid, ppv);

else if(riid == IID_IMultiply)

*ppv = (IMultiply*)this;

else {

*ppv = NULL;

return E_NOINTERFACE;

}

AddRef();

return S_OK;

}

O endereço da interface IUnknown do componente agregado é mantido na variável interna

privada m_pUnknownInner. Quando o componente agregado é activado em

CCalculator::Init() o endereço da interface IUnknown do agregante é passado (2º

argumento) a CoCreateInstance(), que por sua vez o passará ao método

CFactory::CreateInstance() do agregado, para que este saiba que está a ser agregado e

quem é o agregante.

Embora o agregante não implemente a interface do agregado, reconhece-a em

QueryInterface(), passando simplesmente a chamada para o agregado.

Para que o componente agregado funcione bem nessa situação, terá de tomar nota de uma

das interfaces do agregante, de forma a poder retornar os endereços dessas interfaces

quando o cliente executa QueryInterface() numa das interfaces do agregado. Por outro

lado, as chamadas a AddRef() e Release() feitas em qualquer interface, incluindo as do

agregado, deverão actuar no contador de referências do agregante, excepto quando o

agregante está a ser destruído. Nesta última situação, o agregante deverá ser capaz de

efectuar uma operação de Release() no agregado, para também provocar a sua destruição.

Uma forma de contemplar todos estes requisitos é implementar nos componentes que

suportam agregação duas interfaces com os métodos de IUnknown. Pode ver-se a seguir

uma dessas implementações para o componente Adder que suporta a interface ISum.

Mostram-se apenas as principais diferenças relativas ao código da implementação

apresentado atrás, na secção 1.6.

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 16

. . .

interface INoAggregationUnknown { // interface interna

virtual HRESULT __stdcall QueryInterface_NoAggregation(REFIID riid,

void** ppv)=0;

virtual ULONG __stdcall AddRef_NoAggregation()=0;

virtual ULONG __stdcall Release_NoAggregation()=0;

};

class CAdder : public ISum, public INoAggregationUnknown {

public:

ULONG __stdcall AddRef();

ULONG __stdcall Release();

HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);

ULONG __stdcall AddRef_NoAggregation();

ULONG __stdcall Release_NoAggregation();

HRESULT __stdcall QueryInterface_NoAggregation(REFIID riid, void** ppv);

HRESULT __stdcall Sum(int x, int y, int* retval);

CAdder(IUnknown* pUnknownOuter);

~CAdder() { g_cComponents--; }

private:

ULONG m_cRef;

IUnknown* m_pUnknownOuter;

};

CAdder::CAdder(IUnknown* pUnknownOuter) : m_cRef(1) {

g_cComponents++;

if (pUnknownOuter != NULL)

m_pUnknownOuter = pUnknownOuter;

else

m_pUnknownOuter = (IUnknown*)(INoAggregationUnknown*)this;

}

HRESULT CAdder::QueryInterface_NoAggregation(REFIID riid, void** ppv) {

if(riid == IID_IUnknown) {

*ppv = (INoAggregationUnknown *)this;

else if(riid == IID_ISum)

*ppv = (ISum *)this;

else {

*ppv = NULL;

return E_NOINTERFACE;

}

((IUnknown*)(*ppv))->AddRef();

return S_OK;

}

ULONG CAdder::AddRef_NoAggregation() {

return ++m_cRef;

}

ULONG CAdder::Release_NoAggregation() {

if(--m_cRef != 0)

return m_cRef;

delete this;

return 0;

}

ULONG CAdder::AddRef() {

return m_pUnknownOuter->AddRef();

}

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 17

ULONG CAdder::Release() {

return m_pUnknownOuter->Release();

}

HRESULT CAdder::QueryInterface(REFIID riid, void** ppv) {

return m_pUnknownOuter->QueryInterface(riid, ppv);

}

. . .

HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, REFIID riid,

void** ppv) {

if(pUnknownOuter != NULL && riid != IID_IUnknown)

return CLASS_E_NOAGGREGATION;

CAdder *pAdder = new CAdder(pUnknownOuter);

if(pAdder == NULL)

return E_OUTOFMEMORY;

HRESULT hr = pAdder->QueryInterface_NoAggregation(riid, ppv);

pAdder->Release_NoAggregation();

return hr;

}

. . .

A interface interna INoAggregationUnknown implementa efectivamente os métodos da

interface IUnknown (com nomes diferentes), retornando o endereço deste objecto em

QueryInterface(), e actuando no contador de referências próprio, nos métodos AddRef() e

Release(). Estes 3 métodos, na verdadeira interface IUnknown, simplesmente invocam os

mesmos métodos através do apontador pUnknownOuter. Este apontador, inicializado no

constructor, refere-se à interface IUnknown do agregante se este objecto tiver sido agregado,

ou aos métodos de INoAggregationUnknown, se tiver sido activado directamente de um

cliente.

Repare-se também, que quando este componente está agregado, uma chamada a

QueryInterface(), AddRef() ou Release(), usando a interface ISum, acaba por executar o

correspondente método do agregante. No entanto, o agregante possui um apontador para a

interface INoAggregationUnknown, mascarada de IUnknown, que lhe permite executar

invocações directas aos métodos reais QueryInterface() e Release().

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 18

3. Type libraries

A fim de possibilitar a utilização de objectos COM em linguagens e ambientes de

desenvolvimento não tão poderosos como o C++ é necessário criar e registar uma versão

binária do ficheiro .idl, que contenha a descrição das interfaces suportadas, e classes que

as implementam, juntamente com os respectivos identificadores (GUIDs). Estas versões

binárias chamam-se type libraries e a sua existência permite também a utilização de

marshallers (serializadores da informação necessária à invocação remota de métodos

noutros processos, na mesma máquina ou noutras) fornecidos pelo middleware COM.

3.1 Construção de uma type library

As type libraries são construídas pelo compilador de IDL (midl) desde que o ficheiro fonte

contenha a informação relativa a uma classe (além das interfaces) e a indicação das

interfaces aí implementadas.

Por exemplo, um ficheiro .idl especificando uma classe suportando as duas interfaces ISum

e IMultiply utilizadas nos exemplos anteriores, poderia ser:

// calculator.idl

import "unknwn.idl";

[ object, uuid(10000001-0000-0000-0000-000000000001), oleautomation ]

interface ISum : IUnknown

{

HRESULT Sum(int x, int y, [out, retval] int* retval);

}

[ object, uuid(10000011-0000-0000-0000-000000000001), oleautomation ]

interface ISum : IMultiply

{

HRESULT Sum(int x, int y, [out, retval] int* retval);

}

[ uuid(10000003-0000-0000-0000-000000000001),

helpstring("Calculator Type Library"),

version(1.0) ]

library Calculator

{

importlib("stdole32.tlb");

interface ISum;

interface IMultiply;

[ uuid(10000012-0000-0000-0000-000000000001) ]

coclass Calculator

{

interface ISum;

interface IMultiply;

}

};

Havendo a especificação de uma entrada library e uma entrada, dentro dessa, de

coclass, a compilação do ficheiro .idl produz automaticamente a type library:

> midl /win32 calculator.idl // produz calculator.tlb + …

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 19

Neste caso, os outros ficheiros produzidos (calculator.h, calculator_i.c, …), contêm já

a definição do identificador da classe (CLSID_Calculator), pelo que a sua declaração terá

de ser retirada, quer do ficheiro que implementa o cliente, quer do ficheiro de

implementação do componente. Esses ficheiros também irão conter uma constante com o

identificador da type library (LIBID_Calculator), indicado no ficheiro .idl.

3.2 Registo da type library

Para ser utilizável a type library terá também de ser registada. O seu registo, embora se

possa fazer introduzindo as chaves relevantes directamente na registry (ou com o auxílio

de um ficheiro .reg), tem toda a vantagem em ser efectuado chamando as funções da API

do Windows RegisterTypeLib(), que requer uma chamada prévia a LoadTypeLib(), ou,

de uma só vez uma chamada a:

HRESULT LoadTypeLibEx (

OLECHAR *filename, // nome do ficheiro com a type library (formato wide)

REGKIND kind, // para efectuar o registo usar a constante REGKIND_REGISTER

ITypeLib ** pplib ); // retorna apontador para a interface ITypeLib

Infelizmente não há nenhum programa utilitário que, à semelhança de regsvr32, registe

type libraries a partir de um seu ficheiro (.tlb). É, no entanto, muito fácil escrever um tal

utilitário. Pode ser, por exemplo:

// regtlb.cpp

#include <windows.h>

#include <iostream>

using namespace std;

void main(int argc, char* argv[])

{

if(argc < 2) {

cout << "Usage: regtlb tlbfile.tlb" << endl;

return;

}

CoInitialize(NULL);

OLECHAR psz[255];

MultiByteToWideChar(CP_ACP, 0, argv[1], -1, psz, 255);

ITypeLib* pTypeLib;

HRESULT hr = LoadTypeLibEx(psz, REGKIND_REGISTER, &pTypeLib);

if(FAILED(hr)) {

cout << "LoadTypeLibEx failed." << endl;

return;

}

else

cout << "Type library registered." << endl;

pTypeLib->Release();

CoUninitialize();

}

Outra forma de registar uma type library será embebê-la, como recurso, no ficheiro que

contém o código do componente e usar a função DllRegisterServer() para aí colocar o

código que executa o registo.

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 20

Ao código da função DllRegisterServer() mostrada anteriormente seria fácil acrescentar:

. . .

OLECHAR psz[MAX_PATH];

MultiByteToWideChar(CP_ACP, 0, serverfile, -1, psz, MAX_PATH);

ITypeLib* pTypeLib;

hr = LoadTypeLibEx(psz, REGKIND_REGISTER, &pTypeLib);

if (FAILED(hr))

return hr;

pTypeLib->Release();

. . .

Também é fácil eliminar o registo, a partir do identificador da type library (que foi colocado

no ficheiro .idl e passado para os ficheiros gerados pelo midl). Para colocar o código de

eliminação do registo na função DllUnregisterServer(), basta acrescentar:

. . .

hr = UnRegisterTypeLib(LIBID_Calculator, 1, 0, LANG_NEUTRAL, SYS_WIN32);

if (FAILED(hr))

return hr;

. . .

Para que o registo da type library funcione utilizando regsvr32, é necessário que essa type library esteja presente no ficheiro executável do componente como um seu recurso.

Embeber um recurso num executável windows passa pela criação de um ficheiro de texto

com a descrição do recurso (ficheiro .rc), a sua compilação pelo compilador de recursos (rc),

e finalmente a sua ligação ao código com o linker normal. Se quisermos embeber a type library calculator.tlb no executável do componente a construir (calculator.dll) que

nos tem servido de exemplo, o primeiro passo é a criação de um ficheiro de texto

calculator.rc contendo pelo menos a linha:

1 TYPELIB “calculator.tlb”

De seguida é necessário compilar o recurso e produzir uma sua representação binária

(calculator.res), usando o compilador de recursos:

> rc calculator.rc // produz calculator.res

Finalmente o ficheiro .res deve ser acrescentado aos ficheiros que irão entrar na construção

final do executável do componente:

> cl /LD calculator.cpp calculator_i.c calculator.def calculator.res ...

// + bibliotecas com APIs

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 21

4. Componentes locais e remotos

É possível executar um componente COM num processo separado do cliente, na mesma

máquina, ou então numa outra máquina remota. Qualquer destas hipóteses envolve RPC

e o processo de marshalling da informação necessária entre cliente e componente. Apesar

do overhead de execução ser superior (mesmo na mesma máquina) é por vezes conveniente

optar por esta solução, uma vez que há total isolamento de erros e maior controlo de

segurança.

Para isso podemos desenvolver um componente COM num ficheiro executável .exe em vez

de numa DLL. Como os executáveis não exportam funções, os serviços prestados pelas

funções normalmente exportadas numa DLL de um componente, têm agora de ser

invocados através de argumentos passados à função main().

Felizmente não é necessário desenvolver um novo executável, se necessitarmos de pôr a

funcionar um componente que está numa DLL, num processo separado local ou remoto.

Basta configurar o componente de modo a usar o que se chama um surrogate, que carrega

a DLL num processo separado, como se tratasse de um executável .exe.

Podemos desenvolver os nossos próprios surrogates, mas também podemos sempre utilizar

o que é fornecido com o middleware COM (que se chama dllhost.exe).

4.1 Invocação local via surrogate

A invocação de um componente COM residente numa DLL, já devidamente instalado e

registado, num processo separado do cliente (chama-se a isso activar o componente como

local server, em vez de in-proc server), pode fazer-se acrescentando à registry as duas

chaves seguintes, aqui apresentadas em formato .reg.

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{10000012-0000-0000-0000-000000000001}]

“AppID”=“{10000012-0000-0000-0000-000000000001}”

[HKEY_CLASSES_ROOT\AppID\{10000012-0000-0000-0000-000000000001}]

@=”Calculator Sample”

“DllSurrogate”=””

Claro que estas entradas também podem ser escritas pela função exportada

DllRegisterServer(), existente na DLL do componente.

Além disso o cliente terá de pedir explicitamente a activação de um local server quando

invocar CoCreateInstance():

CoCreateInstance(CLSID_Calculator, NULL, CLSCTX_LOCAL_SERVER,

IID_IUnknown, (void **) &pUnknown);

Se o 3º parâmetro for simplesmente CLSCTX_SERVER será dada prioridade à activação de

um in-proc server.

No entanto, estas alterações podem não bastar para esta activação funcionar. Como se

disse, os componentes em processos separados necessitam de marshalling entre eles e o

cliente. O middleware COM pode fornecer um marshaller genérico, desde que as interfaces

implementadas tenham sido declaradas com algumas restrições nos tipos dos seus

parâmetros e possuam o atributo oleautomation. Esse marshaller irá necessitar de, em

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 22

runtime, conhecer todos os detalhes do componente e suas interfaces, pelo que a type library associada ao componente terá de existir e estar devidamente registada.

Uma alternativa ao marshaller genérico é construir um marshaller específico para o

componente, utilizando os ficheiros gerados pelo midl a partir do ficheiro .idl,

nomeadamente os ficheiros dlldata.c, calculator_i.c e calculator_p.c.

Com estes ficheiros podemos construir uma DLL (que também é um componente COM)

com o código do proxy e stub necessários ao cliente e ao componente:

> cl /LD /DWIN32 /DREGISTER_PROXY_DLL /D_WIN32_WINNT=WINVER dlldata.c

calculator_i.c calculator_p.c calculatorPS.def rpcrt4.lib

/link/OUT:calculatorPS.dll

Poderão ainda ser necessárias as bibliotecas rpcndr.lib e rpcns4.lib.

O ficheiro calculatorPS.def deverá indicar a exportação das 4 funções habituais. Após

esta construção, a DLL produzida deverá ser registada com:

> regsvr32 calculatorPS.dll

Feito o registo e as alterações anteriores, já não será necessária uma type library para

executar o componente num processo separado.

4.2 Invocação remota

Efectuar uma invocação remota é agora mais fácil. Na máquina onde irá executar o

componente (server) este terá de estar registado da mesma forma que no ponto anterior

(como se fosse para execuções locais, mas em processo separado). Terá também de existir e

estar devidamente registado um marshaller, seja ele o genérico (o que implica a existência

e o registo de uma type library) ou o específico.

Na máquina cliente teremos apenas de registar o componente (na chave CLSID) e uma

AppID para esse componente que especifique o nome da máquina remota. Além disso terá

também de existir e estar registado um marshaller para o componente. No caso do

marshaller genérico teremos de instalar e registar a type library; no caso do marshaller

construído especificamente para este componente teremos de o instalar e registar.

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{10000012-0000-0000-0000-000000000001}]

@=”Calculator sample”

“AppID”=“{10000012-0000-0000-0000-000000000001}”

[HKEY_CLASSES_ROOT\AppID\{10000012-0000-0000-0000-000000000001}]

@=”Calculator Sample”

“RemoteServerName”=”nome_ou_endereço_IP_da_máquina”

configuração, na máquina cliente, para activação remota

Se não existir na máquina do cliente um componente local instalado, exactamente o mesmo

código do cliente, modificado como descrito na secção anterior, servirá para a invocação

remota. No caso de existir um componente local e pretendermos, mesmo assim, invocar o

componente numa máquina remota, deveremos usar como 3º parâmetro de

CoCreateInstance() o valor CLSCTX_REMOTE_SERVER.

Notas sobre COM e DCOM

Tecnologias de Distribuição e Integração - Miguel Pimenta Monteiro 23

O administrador das máquinas poderá configurar melhor os valores armazenados na chave

AppID da registry, usando o utilitário dcomcnfg.exe, quer na máquina cliente, quer na

servidora.

Uma forma de não ser necessário o registo do componente remoto e da respectiva chave

AppID na máquina cliente, será a utilização de uma nova versão de CoCreateInstance(),

chamada CoCreateInstanceEx(). Nesta nova API de activação é possível, no caso remoto,

indicar directamente o nome, ou endereço IP, da máquina servidora. No entanto, isto não

dispensa a existência e registo de um marshaller para o componente remoto, na máquina

cliente.