algumas notas sobre com e dcompaginas.fe.up.pt/~nflores/dokuwiki/lib/exe/fetch.php?... ·...
Post on 19-Nov-2018
217 Views
Preview:
TRANSCRIPT
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.
top related