|
|
Programação COM em .Net – parte II
Apresenta vários aspectos relaccionados com a programação COM em .Net.
Programação COM em .Net - parte II
Nível: Iniciado/Médio
Conteúdo: Introdução à programação COM em .Net
Ferramentas: Visual Studio 2003
Linguagem: C#
No último artigo efectuámos uma introdução à programação COM. Como foi possível averiguar, a utilização de objectos COM a partir de .Net não é muito complicada. Claro que, se estivermos a falar de projectos complexos, a utilização correcta desses objectos não é tão simples como deveria ser...
Hoje vamos terminar a abordagem iniciada no primeiro artigo desta série e vamos falar acerca dos seguintes tópicos:
- Utilização de objectos COM;
- Libertação de recursos: problemas oriundos da utilização do Garbadge Collector em .Net;
- Apartments;
Utilização de objectos COM
Os leitores do primeiro artigo desta série deverão ter ficado com a ideia de que a construção de objectos COM é baseada em interfaces. Por outras palavras, quando queremos construir um objecto COM, devemos expor as funcionalidades desse objecto através de interfaces. Esses interfaces definem um contracto entre o objecto COM e eventuais clientes que venham a utilizar esses componentes.
Na prática podemos definir dois tipos principais de interfaces: os interfaces VTBL (V-Table) e os interfaces IDispatch. Existe ainda um outro tipo (misto) de interface que costuma ser utilizado e que é designado de dual. Mas então qual será a diferença entre estes tipos de interfaces? Bem, para percebermos o papel de cada um destes interfaces, nada melhor do que uma análise às principais características de cada um deles. Comecemos então pelos interfaces VTBL.
Interfaces VTBL
Os componentes que suportam este tipo de interfaces permitem o chamado early binding. Os objectos que possuem este tipo de interfaces não podem ser utilizados por clientes script, devido ao facto de o código script ser interpretado e não compilado. O binding pode ser visto como sendo o processo de relacionar a função que está a ser evocada com o código que implementa essa função. Quando estamos a falar de early binding, estamos a falar de um processo que ocorre aquando da compilação. Nessa altura, todas as referências aos nomes das funções são substituídas pelos respectivos endereços onde se encontram as instruções dessa função.
Os componentes COM organizam os endereços das funções de acordo com uma tabela de apontadores, designada de V-Table. Uma V-Table não é mais do que um array de endereços de memória em que cada entrada representa um método que indica o endereço onde se encontra o código relativo a esse método. Quando obtemos uma referência a um objecto COM, (podemos) também obter um apontador que nos permite aceder à V-Table desse objecto e assim podemos evocar os métodos pretendidos (claro que na prática deixamos esse tipo de coisas para o compilador ;) ). Uma vez que todos os componentes COM têm de herdar obrigatoriamente do interface IUnknonw, então podemos afirmar que as três primeiras entradas numa V-Table serão sempre referentes aos três métodos definidos por este interface. Por exemplo, quando precisamos de evocar o método AddRef, o cliente já sabe a posição desse método na V-Table do componente (devido ao facto de todos os componentes terem o IUnknown como interface de topo) e por isso pode evocar rapidamente esse método.
Como é possível aferir a partir da discussão anterior, se necessitarmos de evocar uma determinada propriedade ou método personalizado (isto é, definido pelo programador do componente através de um interface personalizado), só o poderemos fazer se tivermos conhecimento do V-Table desse componente. Portanto, necessitamos deste tipo de informação quando estamos a compilar a nossa aplicação. Ora daqui decorre aquele que é o único problema da utilização deste tipo de interfaces: necessidade de compilação (que, como é sabido, não está ao alcance de todos os clientes).
Interfaces IDispatch
Para além do IUnknow, o COM também define o interface IDispatch. Os componentes que implementam este interface são conhecidos como dispinterfaces. Como referimos no artigo anterior, este interface apresenta dois métodos muito importantes:
- GetIDsOfNames: permite converter um nome de um método ou propriedade no seu ID, de forma a que seja possível invocar esse elemento;
- Invoke: utilizado para evocar um membro de uma classe, utilizando sempre o ID obtido através do método descrito no item anterior.
A utilização deste interface como forma de expor um conjunto de métodos/propriedade oferece algumas vantagens. Por exemplo, não é necessário conhecer antecipadamente a V-Table do objecto para utilizar o componente. Este é o esquema utilizado pelos clientes script.
Quando definimos um interface do tipo IDispatch, temos de atribuir um Id a cada membro por forma a identificar cada elemento implementado pelo componente. Estes ids eram conhecidos por dispids, e são fundamentais para identificarem cada membro do componente. Do ponto de vista do cliente, sempre que é necessário evocar um membro do componente é necessário:
- Obter o dispid do membro, utilizando para tal o método GetIDsofNames;
- Evocar o método/propriedade utilizando a função Invoke do interface IDispatch.
Nota: como é óbvio, os clientes script continuam a utilizar o V-Table do componente; contudo, neste caso, apenas podem aceder à V-Table relativa ao interface IDispatch, sendo o acesso a todos os outros métodos feitos através da evocação do par GetIDsOfNames/Invoke.
Interfaces Dual
Esta é uma aproximação que, supostamente, agrada a "gregos e a troianos". Com este tipo de interface possibilitamos a utilização do componente através de early binding, caso o cliente suporte esta opção, ou então através de late binding, nos restantes casos. Neste caso, o nosso interface deriva de IDispatch temos de anotar o IDL com o atributo dual e anotar os métodos do componente com o atributo dispid ( o sample que acompanha este artigo utiliza esta estratégia).
Interfaces VTBL vs IDispatch puros
Ambos os casos apresentam vantagens e desvantagens. Os interfaces VTBL apresentam um grande handicap nos dias que correm: não podem ser executados por clientes script. Aliás, esta é grande motivação para construir interfaces IDispatch. Por outro lado, apenas os interfaces VBTL conseguem utilizar todos os tipos existentes a nível do IDL, uma vez que os interfaces utilizados a nível de clientes de script (ou seja, os dispinterfaces) estão limitados a tipos que possam ser representados através de um VARIANT. Para além disto, os objectos que contém interfaces VTBL podem conter vários interfaces, ao contrário do que sucede com os interfaces IDispatch (tecnicamente é possível conter vários interfaces IDispatch; contudo, os clientes script apenas poderão aceder a um desses interfaces, não conseguindo "navegar" para outro interface como acontece no caso de clientes que suportam o early bindging).
O sample que acompanha este artigo contém um objecto que implementa um interface dual. Este interface é muito simples e foi construído como dual para demonstrar a diferença a nível de evocação dos métodos.
Utilização de objectos COM
Como descrevi no último artigo, antes de utilizarmos um objecto COM temos de proceder à criação dos RCW (Runtime Callable Wrappers). Isto pode ser feito através do Visual Studio ou, alternativamente, através do tlbimp. No caso de estarmos a trabalhar com interfaces do tipo dual ou VTBL, a utilização é bastante simples; contudo, no caso dos IDispatch puros torna-se necessário recorrer a métodos da classe Type.
O código que acompanha este artigo contém duas samples: VTBLBind e DispatchBind. Em cada uma delas é possível observar os passos necessários à criação dos tipos utilizando ambos os tipos de binding. Antes de explicarmos os pormenores de cada um dos projectos, vamos definir o nosso componente, utilizando para tal o IDL:
// TestProject.idl : IDL source for TestProject
//
// This file will be processed by the MIDL tool to
// produce the type library (TestProject.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(BF9137C8-604E-46BD-BF65-B9CC370AA1A8),
dual,
nonextensible,
helpstring("ICarDual Interface"),
pointer_default(unique)
]
interface ICarDual : IDispatch{
[propget, id(1), helpstring("Gets or sets the top speed")] HRESULT TopSpeed([out, retval] LONG* pVal);
[propput, id(1), helpstring("Gets or sets the top speed")] HRESULT TopSpeed([in] LONG newVal);
[propget, id(2), helpstring("Gets or sets the brand of the car")] HRESULT Brand([out, retval] BSTR* pVal);
[propput, id(2), helpstring("Gets or sets the brand of the car")] HRESULT Brand([in] BSTR newVal);
[propget, id(3), helpstring("Gets the current speed of the car")] HRESULT CurrentSpeed([out, retval] LONG* pVal);
[id(4), helpstring("Reduce the speed to the indicated value")] HRESULT Break([in] LONG speed);
[id(5), helpstring("Speeds up the car to the correct speed")] HRESULT SpeedUp([in] LONG newSpeed);
};
[
uuid(C58F3269-B512-4345-80C3-F064C4390CBE),
version(1.0),
helpstring("TestProject 1.0 Type Library")
]
library TestProjectLib
{
importlib("stdole2.tlb");
[
uuid(564733F8-D181-4E35-A9DF-FA4A6DD00EF2),
helpstring("CarDual Class")
]
coclass CarDual
{
[default] interface ICarDual;
};
};
Como é possível verificar, o componente implementa apenas um interface, do tipo dual. Este interface apenas contém duas propriedades de leitura e escrita (TopSpeed e Brand), uma propriedade de leitura (CurrentSpeed) e dois métodos (Break e SpeedUp).
Como é possível verificar através do código da sample VTBLBind, o acesso a este objecto através de early binding é feito de forma praticamente transparente. Todas as particularidades do COM estão escondidas pelo RCW e até parece que estamos a trabalhar em VB6 ou em C++ com (smart pointers). Contudo, a situação muda radicalmente quando utilizamos o componente através do interface IDispatch. Ao contrário do que acontece com o VB6, em que criamos o objecto com a função CreateObject e, em seguida, trabalhamos normalmente com o objecto (ou seja, acedemos às suas propriedades como se tivéssemos utilizado early binding), em .Net temos de recorrer aos métodos contidos nas classes Type/Activator e que nos permitem evocar os vários métodos/propriedades desse tipo de elementos,
Apenas um aparte para indicar que, apesar de tudo, esta situação é bem mais agradável do que quando temos de aceder a IDispatch puros a partir de C++.
Utilização do interface IDispatch "puro"
Bem, a primeira pergunta que se coloca é: como criamos um objecto que expõe um interface IDispatch? Antigamente teríamos de utilizar o CoCreateInstance (ou uma variante deste método) se estivéssemos a utilizar o C++ ou então o CreateObject, se o projecto fosse construído em VB. Em .Net, a forma de proceder à criação destes componentes consiste em utilizar alguns métodos da classe Type e Activator. Uma vez que existem várias operações que são repetidas quando trabalhamos com este tipo de interfaces, optei por construir um wrapper em torno dessas mesmas operações. É essa a principal função da classe LAInteropUtils. Esta classe contém métodos que permitem obter/modificar valores de propriedades, evocar métodos, criar instâncias, etc.
Comecemos então pela criação. O método LAInteropUtils.CreateObject contém o seguinte código:
public static object CreateObject( string progId )
{
Type type = Type.GetTypeFromProgID( progId, true );
return Activator.CreateInstance( type );
}
Este método começa por tentar obter o tipo representado pelo ProgId e em seguida tenta criar uma instância desse tipo. Até aqui nada de novo. As novidades surgem apenas na evocação das propriedades e métodos (especialmente para os programadores oriundos do VB). Existem vários métodos utilizados para encapsular este tipo de acesso. Assim, quando necessitamos de obter o valor de uma propriedade, devemos recorrer ao método GetPropertyValue. Para obtermos a velocidade actual podemos utilizar o seguinte código:
int aux = Convert.ToInt32( LAUtils.LAInteropUtils.GetPropertyValue( _car, "CurrentSpeed" ) );
Por sua vez, o método GetPropertyValue (ou melhor, um dos seus overloads) contém as seguintes instruções:
public static object GetPropertyValue( object caller, string propertyName, object [] vals )
{
return AuxiliaryInvokeMethod( caller, propertyName, BindingFlags.GetProperty, vals );
}
private static object AuxiliaryInvokeMethod( object caller, string memberInfo, BindingFlags flags, object [] parameters )
{
Type aux = caller.GetType();
return aux.InvokeMember( memberInfo, flags, null, caller, parameters);
}
O método GetPropertyValue recorre ao método AuxiliaryInvokeMethod para efectuar os cálculos necessários à obtenção do valor da propriedade. O grande responsável pelo trabalho de obtenção do valor da propriedade é o método InvokeMember da classe Type. Este método (que também pode ser utilizado sobre tipos .Net) permite definir o nome e o tipo de membro que está a ser evocado (primeiro e segundo parâmetros).
Apesar de tudo isto parecer muito estranho para obter apenas o valor de uma propriedade (especialmente para todos aqueles que sempre utilizaram VB), não é mais do que o procedimento normal para acesso a propriedades definidas através de um dispinterface. Aliás, aqueles que costumavam utilizar o C++ para aceder a este tipo de interfaces devem estar bastante contentes com as facilidades fornecida pelos membros das classes Type/Activator (bem, pelo menos eu estou muito contente com estes métodos! :) ). Os métodos contidos na classe LAInteropUtils tentam encapsular todos os pormenores relativos à utilização deste tipo de interfaces a partir de .Net, pelo que se redirecciona o leitor para uma consulta desta classe de forma a obter mais pormenores.
Libertação de recursos
Apesar de todas as facilidades fornecidas pela framework para integração de objectos COM, existe um pequeno grande problema na utilização deste tipo de objectos. Estou a referir-me ao facto de estarmos a integrar duas tecnologias com diferentes tipos de finalização. Como é do conhecimento geral, os objectos .Net não são determinísticos. Logo não podemos prever a altura em que é feita a destruição de instâncias dessas classes. Já os componentes COM necessitam de uma gestão determinística, em que o tempo de vida do elemento é determinado por um contador interno que é incrementado sempre que o utilizador evoca o mótodo AddRef e decrementado sempre que o utilizado chama a função Release. É da responsabilidade do programador utilizar correctamente estas funções.
É nesta diferença que residem os principais problemas de Interop entre .Net e COM. Para percebermos melhor a razão de tais problemas, temos de, em primeiro lugar, perceber o funcionamento interno dos RCW e compará-los com o funcionamento dos objectos COM. Começando pelos objectos COM, podemos afirmar que sempre que passamos uma referência (por exemplo, através do operador =) devemos sempre incrementar o contador interno do objecto. Obviamente devemos decrementar essa referência sempre que já não necessitamos desse componente. Este tipo de operações é feito de forma automática se estivermos a utilizar o VB ou os smart pointers (em C++).
Por outro lado, os RCW efectuam a gestão (interna) de um elemento COM de forma diferente. Ao contrário do que acontece normalmente, os RCW mantém apenas uma única referência ao objecto COM (ou seja, o contador interno deste objecto está sempre igual a um) independentemente do número de referências efectuadas ao objecto RCW. Por outras palavras, código deste tipo:
RCWRef myVar = obter_referencia_para_Wrapper;
RCWRef myVar2 = myVar;
Resulta sempre numa contagem do objecto COM contido no wrapper igual a um. Então como é que o GC sabe que pode destruir o wrapper e, por sua vez, o objecto COM que está no seu interior? Simples: o GC efectua a sua gestão normalmente de acordo com as suas regras relativas à limpeza de memória. Por outras palavras, o wrapper está sujeito às mesmas regras de uma instância de uma classe definida em .Net. Se necessitarmos de libertar um objecto COM de forma explicita, podemos recorrer ao método ReleaseComObject da classe Marshal . A evocação deste método irá libertar todas as referências relativas a este objecto COM. Ao executarmos este método, obtemos um número inteiro que nos permite averiguar acerca da existência de uma outra contagem interna. O objecto COM apenas será eliminado quando este método (ReleaseComObject) retornar zero. Mas então que valor é este retornado pelo objecto método ReleaseComObject? Ao que consta, está relacionado com uma contagem interna efectuada pelo wrapper que indica o número de vezes que foi feito o marshalling da referência COM contida no wrapper. Portanto pode ser necessário evocá-lo várias vezes até que o objecto seja eliminado. Se quisermos, podemos construir uma rotina genérica que liberta qualquer objecto COM: basta evocar sucessivamente o método ReleaseComObject até que o valor retornado seja zero - este tipo de rotina está encapsulado no método ReleaseComObjecto da classe LAInteropUtils. Até hoje continuo sem perceber porque é que a Microsoft não nos forneceu uma rotina deste género...
Só uma nota final: após utilizarmos a rotina LAInteropUtils.ReleaseComObject, libertamos os recursos COM, fazendo com que eventuais acessos aos wrappers gerem excepções. Por exemplo:
RCWRef myVar = obter_referencia_para_Wrapper;
RCWRef myVar2 = myVar;
... //codio aqui
LAInteropUtils.ReleaseComObject( myVar );
myVar2.Test = "UUU"; //excepção Gerada aqui!!!!
No excerto anterior geramos uma excepção se tentarmos aceder à variável myVar2 após evocarmos o método LAInteropUtils.ReleaseComObject. Portanto, quando desenvolvemos uma aplicação complexa há que ter muito muito cuidado com a libertação dos recursos COM. Este tipo de código é, na minha opinião, um regresso ao passado! Há quem pense que podemos tornar este código mais object oriented de acordo com as regras da framework. Para tal basta construir um wrapper (em torno do RCW) que iria implementar o interface IDisposable. Bem, actualmente todas as minhas soluções relacionadas com COM utilizam um conjunto de métodos muito semelhantes aos que apresento no sample e, até hoje, tudo tem funcionado bem. A constante utilização do método LAInteropUtils.ReleaseComObject espalhado ao longo do código é um mal com o qual tenho convivido nestes últimos tempos...enfim, nada a fazer se quiser continuar a utilizar o C# (o que não será por muito tempo, pois o pouco que vi de managed C++ já me convenceu a passar a utilizar essa linguagem).
É devido a estes factores que ambas as samples que utilizam o objecto COM contém chamadas ao método LAInteropUtils.ReleaseComObject por forma a proceder às necessárias operações de limpeza. Só para terminar esta secção, gostaria de redireccionar o leitor para uma discussão muito interessante sobre o porquê da não implementação de wrappers que implementassem o padrão IDisposable que se encontra no blog do Sam Gentile.
Utilização de Apartments
A utilização de componentes COM em ambientes multithreading foi, desde que me consigo lembrar, motivo de grande preocupação entre os programadores. A escrita destas linhas já me trouxe recordações (algo desagradáveis) do único componente COM multithreaded que fiz até hoje. Se bem me recordo, as minhas palavras finais após a conclusão do dito cujo foram: ".*$*!$**...nunca mais". Presumo que isto resume, de uma forma mais ou menos sintetizada, o gozo que obtive na construção desse componente ;).
Contudo, a utilização de componentes COM neste tipo de ambientes (multithreaded) segue um conjunto de regras bem definidas. A colocação de um componente num determinado apartment (se não sabem o que é um apartment, não se preocupem que já vou tentar explicar este conceito) depende dum conjunto de aspectos definidos pelo próprio objecto COM e pelo cliente que acede a esse objecto.
Para os que já não estão recordados, quando utilizamos um componente COM temos sempre de inicializar a thread onde o componente irá ser utilizado. Para tal tínhamos de recorrer ao método CoInitialize(Ex), algo que, para variar era feito automaticamente em VB6. Por outro lado, os componentes COM também definiam o tipo de acesso a que pretendiam estar sujeitos, utilizando para isso um conjunto de entradas no registry (ou, no caso de estamos a falar de componentes EXE, utilizando também o método CoInitialize(Ex); contudo, para simplificar, vamos falar apenas de servidores dll até ao final do artigo).
Quando um objecto COM é inicializado na aplicação cliente, ambas as regras definidas pelo cliente (através da inicialização da thread feita com o método CoInitialize(Ex)) e pelo próprio componente são comparadas; se estas forem diferentes, então o cliente e servidor COM ficarão em apartments diferentes, sendo a comunicação entre eles efectuada através de um proxy (o que introduz a necessidade de efectuar o chamado marshaling). Caso contrário (quando estão no mesmo apartment), a comunicação entre o cliente e o servidor é feita de forma directa, sem necessidade de utilizar intermediários. A beleza desta solução reside no facto de não ser necessário ao cliente ter conhecimento prévio sobre as características do componente COM (e vice-versa).
Mas então o que é essa coisa chamada de apartment? Provavelmente a forma mais fácil de perceber o conceito consiste em considerar um apartment como sendo um contentor lógico situado no interior de um processo utilizado por componentes COM que têm determinados requisitos quanto ao nível de acesso por parte das várias threads (que podem existir dentro de um processo). É importante não esquecer que um objecto é colocado num apartment durante a sua criação e aí irá residir até que seja destruído.
O COM permite a colocação de um objecto num de três apartments:
- STA (Single threaded apartment): neste caso, a framework COM garante que o acesso aos componentes colocados neste tipo de apartment nunca será efectuado por mais do que uma thread (ou seja, nunca iremos ter várias threads a acederem simultaneamente a um método de um componente); o acesso a este tipo de componentes é sempre feito por intermédio da thread que inicializou o apartment. Devido a isto é normal afirmar-se que os componentes STA têm thread-affinity. Um processo pode ter vários apartments deste tipo (para isso basta criar várias threads e inicializá-las de acordo com este tipo de apartment -mais detalhes à frente). Antes de passarmos às MTAs, falta referir que um apartment STA pode conter vários componentes COM deste tipo (para tal, basta criar vários objectos COM numa thread que tenha sido inicializada para este tipo de apartment);
- MTA( Multi-threaded apartment): como o próprio nome indica, os componentes colocados neste tipo de apartment podem ser acedidos por várias threads ao mesmo tempo. Devido a isto, o programador do componente tem de efectuar o sincronismo no acesso aos dados internos do componente (utilizando para tal as várias primitivas de sincronização existentes). Um processo tem, no máximo, um apartment deste tipo onde residem todos os objectos que sejam concebidos para este tipo de apartment.
- TNA(Thread Neutral apartment): este é o último tipo de apartment. Ao contrário do que acontece com os dois tipos anteriores, este apartment não "possui" nenhuma thread. O acesso a este tipo de objectos é sempre feito através de um proxy especial. A vantagem deste apartment reside no facto de as chamadas a métodos deste componente não requererem o chamado thread switch, sendo por isso muito mais rápidas do que quando temos de efectuar a comunicação com um componente que está num apartment diferente da thread "cliente". Por outras palavras, este tipo de componentes será sempre evocado na thread "cliente" (se estivermos a chamar um método a partir de uma thread STA, então essa evocação será efectuada nessa thread STA; se estivermos a evocar o método a partir de uma thread MTA, então essa evocação será efectuada nessa mesma thread MTA).
Agora só falta mesmo explicitarmos como é que o cliente e o servidor COM definem o apartment que querem utilizar! Vamos então começar pelo cliente...
Definição de uma apartment do cliente
Como referimos acima, um cliente define o tipo de apartment que deseja criar através do método CoInitialize(Ex). O método CoInitializeEx aceita um parâmetro que define o tipo de apartment:
- COINIT_APARTMENTTHREADED: neste caso, a thread irá ser associada a um apartment do tipo STA.
- COINIT_MULTITHREADED: neste caso, a thread está a inicializar (ou a juntar-se) a um apartment MTA.
Portanto, um cliente antes de aceder a um objecto COM tem de evocar o método CoInitialize(Ex) indicando qual o tipo de apartment ao qual deseja associar a thread. É importante referir que o método CoInitialize tem o mesmo comportamento do método CoInitializeEx( NULL, COINIT_APARTMENTTHREADED).
Definição do apartment feita pelo componente COM
A escolha do apartment é feita através de uma chave do registry situada em CLSID\InprocServer32 designada de ThreadingModel. Ao contrário do que o leitor já deve estar a suspeitar, podemos atribuir cinco (e não três!) valores a esta entrada:
- Single: neste caso o componente deve ser colocado na primeira STA criada pelo processo; este valor era utilizado pelos primeiros componentes desenvolvidos em VB.
- Apartment: componentes devem ser colocados obrigatoriamente numa STA.
- Free: componentes devem residir na MTA do processo;
- Both: ao contrário do que o nome indica, o componente deve residir no apartment associado à thread onde ele foi criado; provavelmente a Microsoft deveria ter utilizado o nome Either para este tipo de componente.
- Neutral: componente deve estar associado ao apartment NTA.
Apartments em .Net
Na prática as nossas aplicações .Net não são mais do que clientes dos componentes COM. Devido a isso regem-se pelas mesmas regras definidas na secção sobre apartments no cliente. Aliás, os mais atentos devem ter reparados que as aplicações Windows Forms geralmente contém código semelhante ao seguinte:
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
Neste caso, a thread está a ser associada a um apartment do tipo STA. Bem, de certo que não será nenhuma surpresa se eu afirmar que também existe um atributo MTAThreadAttribute, que associa uma thread ao apartment MTA do processo. De referir que, em aplicações .Net podemos aceder a uma threading pool (por exemplo, quando utilizamos um delegate) ou então, podemos criar uma thread explicitamente através da classe Thread. Sem me querer alongar sobre este assunto, quero apenas referir que apenas quando criamos uma thread de forma explicita podemos controlar o tipo de apartment que será associado à thread. Se utilizarmos uma thread proveniente da thread pool, então não teremos hipótese de configurar o apartment. Essa thread estará, por defeito, sempre associada à MTA do processo.
Como é óbvio, a definição do apartment é apenas utilizada quando utilizamos componentes COM nessa thread.
Conclusões finais
Com este artigo aprofundámos os nossos conhecimentos sobre o Interop entre .Net e COM. Falámos sobre três tópicos importantes:
- tipos de interfaces e respectiva evocação de métodos a partir de .Net: como foi possível observar, a evocação de métodos contidos em interfaces VTBL não oferece nenhum tipo de dificuldade; o mesmo não se pode dizer em relação aos métodos oriundos de um dispinterface puro.
- Limitações decorrentes da forma como funciona o garbadge collection em .Net: .Net e COM tem filosofias muito diferentes no que diz respeito à finalização; apesar de não ser tão fácil como devia (isto é só a minha opinião!), a framework .Net permite-nos terminar explicitamente a utilização de um objecto COM, libertando assim os recursos utilizados por esse objecto.
- Apartments: esta foi sempre uma das áreas mais complicadas para os programadores COM. Foi sempre uma área rodeada de imensos rumores, muitos dos quais sem fundamento!
Antes de concluir, só (mais) uma observação: o leitor pode ter ficado com a ideia de que a construção de componentes MTA resolve todos os problemas existentes a nível de performance. Tal não é verdade! É preciso não esquecer que se o cliente definir a sua thread como STA e o nosso componente for configurado como MTA introduzimos sempre o chamado thread switch quando o cliente evocar um método no componente. Provavelmente neste caso obteríamos melhores resultados se o componente fosse configurado para ser utilizado numa STA (porque neste caso iria ficar no mesmo apartment do que o cliente, eliminando-se assim o thread switch e a utilização de proxy!)
Onde estamos?
Bem, por hoje é tudo. Apesar de no artigo anterior ter prometido que neste iríamos abordar aspectos relacionados com a construção de componentes .Net para serem utilizados em aplicação COM, achei por bem aprofundar um pouco mais os aspectos relacionados com a utilização de componentes COM em .Net, ficando assim a construção de componentes .Net para utilização em aplicações COM para um próximo artigo.
Por favor enviem-me as vossas opiniões/sugestões/críticas/correcções para progC@netmadeira.com.
Fiquem bem e boa programação! Até à próxima.
Leiam o meu blog em: http://weblogs.pontonetpt.com/luisabreu
Written By: labreu
Date Posted: 4/14/2006
Number of Views: 403
Return
|
|
|
|
|
|
|