Artykuł ten krok po kroku omawia dostęp do COM+ za pomocą języka C#. Ma on na celu zapoznanie czytelnika z możliwościami wykorzystania obiektów COM+ napisanych w dowolnym języku programowania. Artykuł składa się z trzech zasadniczych części opisujących poszczególne metody wywołania metod obiektu COM+: „early binding”, „late binding”, „RealProxy”.
„Dostęp do obiektów COM+ z poziomu ASP .NET”
Wstęp
Systemy rozproszone lub większe aplikacje oparte na technologii COM+ są popularne wielu powodów. Jednym z nich jest fakt, że logikę biznesową mogą realizować poszczególne komponenty. Dużym atutem aplikacji zbudowanych w technologii COM+ jest możliwość używania różnych języków programowania (komponenty COM+ komunikują się ze sobą za pomocą „binarnego kontraktu” (ang. binary contract)).
Jednak COM+ ma jedną poważną wadę – nie można uzyskać programowego dostępu do komponentu w czasie działania aplikacji. Natomiast platforma .NET dzięki pojęciu refleksji [3] taki dostęp umożliwia. Inną cechą .NET przewyższającą COM+ jest jednolity mechanizm wyjątków, które są generowane dla wszystkich błędów. Pisząc aplikacje w Visual Studio 6.0 możemy wywoływać metodę w różnych API. Jeżeli API zwróci kod błędu musimy mieć wiedzę jakie błędy mogą wystąpić w danym API. Problem ten rozwiązuje jednolity mechanizm wyjątków. Ponadto wszystkie aplikacje pisane dla .NET (również komponenty COM+ napisane w C#) są uruchamiane we wspólnym środowisku zw. Common Language Runtime (CLR). Głownie z tych powodów platforma .NET powoli wypiera technologie COM+. Dlatego bardzo ważną rzeczą jest zachowanie możliwości współpracy pomiędzy tymi dwoma technologiami.
W tym artykule chciałbym pokazać, iż dostęp do komponentów COM+ z poziomu platformy .NET jest bardzo łatwy. W tym celu zdecydowałem się wybrać ASP .NET i VB 6.0 jako przedstawicieli tych dwóch technologii. Przykładowe aplikacje mają na celu pokazanie, że .NET umożliwia dostęp do aplikacji COM+ za pomocą dowolnego języka wspieranego przez .NET, bez zmiany kodu w istniejącej aplikacji COM+.
Dostępu do komponentu COM+ możemy potrzebować nie tylko w sytuacjach kiedy budujemy nową aplikację np. w ASP .NET i chcemy skorzystać ze sprawdzonego wcześniej modułu, ale również wtedy, gdy zajmujemy się utrzymywaniem jakiegoś dużego systemu. W tej sytuacji możemy być zmuszeni do napisania aplikacji za pomocą, której będziemy mogli administrować aplikacją i za jej pomocą wykonywać różne zadania (np. wywołać daną metodę lub metody i sprawdzić jak się zachowuje nasz system).
Zaczynamy
By zacząć przygodę z wywoływaniem metod COM+ napiszmy napisać prosty komponent COM+. W tym celu tworzymy projekt ActiveX w Microsoft Visual Studio 6.0. Oto kod tego komponentu.
[Kod VisualBasic]
Public Function Class_Sum(Par1 As Integer, Par2 As Integer) As Integer
Class_Sum = Par1 + Par2
End Function
Public Function Class_Sub(Par1 As Integer, Par2 As Integer) As Integer
Class_Sub = Par1 - Par2
End Function
Public Function Class_Mul(Par1 As Integer, Par2 As Integer) As Integer
Class_Mul = Par1 * Par2
End Function
Public Function Class_Div(Par1 As Integer, Par2 As Integer) As Integer
If Par2 = 0 Then
Class_Div = -1
Else
Class_Div = Par1 / Par2
End If
End Function |
Ostatnią czynnością, jaką wykonamy by móc skorzystać ze stworzonego komponentu jest dodanie go do aplikacji COM+. Proces tworzenia i rejestrowania komponentów COM+ opisany jest w artykule [2].
Po wykonaniu tych czynności możemy przejść dalej i napisać aplikacje www, które będą korzystać z metod udostępnianych przez komponent.
"Earty binding"
Komponenty w .NET komunikują ze sobą poprzez pakiety (ang. assemblies). By komponent napisany w .NET mógł komunikować się z komponentem COM+ trzeba wygenerować assembly wrapper zw. „Runtime Callable Wrapper” (RCW) na podstawie biblioteki typów COM (COM type library (TypeLib)). Możemy tego dokonać na dwa sposoby: używając polecenia TlbImp.Exe (Type Library Importer) [5], lub bezpośrednio w Visual Studio 2003 dodając referencje do obiektu COM+. Zasadniczą różnicą pomiędzy tymi dwoma sposobami jest to, iż za pomocą polecenia TlbImp możemy podać nazwę RCW, z kolei VS przyjmie nazwę pliku .dll, czyli w naszym przypadku prjMTSDebug.
Po uruchomieniu VS tworzymy nowy projekt C# ASP WEB Application, który nazywamy EarlyBindingTest. Przed użyciem komponentu w tej aplikacji należy utworzyć RCW (Runtime Callable Wrapper), Głównym zadaniem RCW jest umożliwienie komunikacji pomiędzy platformą .NET a COM+ (m.in. zarządzanie czasem życia obiektu COM+, umożliwienie wywołania funkcji COM+ bez konieczności podania do jakiego interfejsu ona należy, odpowiadanie za przygotowanie wywołania metody COM+ z poziomu .NET. (m.in. konwersje typów).
Po wykonaniu tej czynności nie pozostaje nic innego jak umieszczenie na formularzu odpowiednich kontrolek by móc przetestować komponent.

rys. 1. Strona do testowania komponentu COM+
Gdy kontrolki zostały już umieszczone na stronie należy obsłużyć zdarzenie po naciśnięciu przycisku wynik, poniżej zamieszczam kod tej funkcji:
[Kod C#]
private void btSubmit_Click(object sender, System.EventArgs e)
{
if (Page.IsValid )
{
if (ChooseOp.SelectedIndex != 0)
{
try
{
//pobranie parametrów z pól tekstowych
//pominięte dla czytelności)
operations = (Operations)ChooseOp.SelectedIndex;
switch(operations)
{
case Operations.Addition:
res = COMclass.Class_Sum(ref par1, ref par2);
break;
case Operations.Subtraction:
res = COMclass.Class_Sub(ref par1, ref par2);
break;
case Operations.Multiplication:
res = COMclass.Class_Mul(ref par1, ref par2);
break;
case Operations.Division:
res = COMclass.Class_Div(ref par1, ref par2);
break;
default:
ErrorMessage = „Wybierz działanie z listy”; break;
}
}
catch(Exception ex)
{
ErrorMessage = ex.Message.ToString();
ErrorMsg.Visible = true;
ErrorMsg.Text = ErrorMessage;
}
tbRes.Text = res.ToString();
}
else
{
ErrorMessage = „Wybierz działanie z listy”;
lbErrorMsg.Visible = true;
lbErrorMsg.Text = ErrorMessage;
}
}
}
|
Jak łatwo można zauważyć, po dodaniu referencji do obiektu COM+ (do projektu), można się do niego odwoływać jak do instancji klasy z tą różnicą, że wartość zwracana przez funkcje COM+ jest typu object i musimy ją rzutować na żądany typ.
Pokazane podejście ma dość dużo wad. Mianowicie:
O nie można zaimplementować interfejsu IDisposable do obiektu COM+, co zapewni jego automatyczne zwolnienie po wykonaniu kodu w bloku using,
O nie można zagwarantować, że referencja do COM będzie usunięta, gdyż nie można zaimplementować ~Desktruktora dla referencji COM,
O kiedy dodajemy referencje COM+ do VS jest ona zależna od wersji. Po dokonaniu zmian w istniejącym komponencie COM+ samo przekompilowanie nie wystarczy. Należy wtedy usunąć referencje do komponentu COM+ z naszej aplikacji i dodać ponownie. Dzieje się tak, dlatego iż w momencie dodania referencji VS tworzy RCW, który umożliwia komunikację pomiędzy komponentami .NET a COM+.
W tym momencie nasuwa się pytanie. Czy w sytuacji, kiedy dokonamy zmian w komponencie COM+, którego używamy w aplikacji musimy usunąć referencję, skompilować komponent i dodać ponownie referencje? Otóż nie. .NET umożliwia programowy dostęp do COM+ dzięki mechanizmowi refleksji i mechanizmowi „Late binding”, który omówię za chwile.
"Late binding"
W poniższej części artykułu zaprezentuje jak w 4 prostych krokach, można wykorzystać mechanizm refleksji do uzyskania programowego dostępu do komponentu COM+.
O pobieramy interfejs IDispatch poprzez wywołanie metody Type.GetTypeFromProgId(string ProgID),
O tworzymy instancję używając typeID Activator.CreateInstance(),
O tworzymy tablice argumentów (wywołania, formalnych) jeżeli są potrzebne,
O wywołujemy metodę
Metoda Type.GetTypeFromProgId(string ProgID) służy do pobrania typu związanego z podanym identyfikatorem programu (ang. program identifier - (ProgID)). Z kolei metoda Activator.CreateInstance() zwraca instancję obiektu na podstawie podanego typu. Więcej informacji na temat działania mechanizmu refleksji można znaleźć w artykule [3] i [7] lub MSDN.
W celu zademonstrowania tego mechanizmu stwórzmy klasę, której instancja będzie przechowywała obiekt COM+, oraz obsługiwała wywoływanie jego funkcji. Definicja klasy wraz ze zmiennymi prywatnymi jest przedstawiona poniżej:
[Kod C#]
public class COMWrapper :IDisposable
{
//zmienna przechowująca obiekt COM+, którego metody będziemy wywoływać
private object _COM;
//Typ obiektu przechowywanego w zmiennej COM, uzyskany metoda
GetType()
private Type _COMType;
//Metoda, którą chcemy wywołać pobrana z typu obiektu metoda GetMethod
private MethodInfo _Method;
//Kolekcja członków obiektu zwrócona przez metodę FindMemebers
private MemberInfo[] _Members;
//Flaga binarna informująca, czy obiekt został już zwolniony
private bool _IsDisposed = false;
[…]
} |
Kolejną czynnością jaką musimy wykonać jest zaimplementowanie metod. Podążając zgodnie z planem zamieszczonym na początku tej części artykułu zaczniemy od tworzenia obiektu COM+. Kod metody tworzącej obiekt COM+ znajduje się poniżej.
[Kod C#]
private object CreateCOM( string progID )
{
// Tworzymy instancje obiektu COM+ na podstawie progID
//1. Pobieramy typ obiektu, który chcemy utworzyć
_COMType = Type.GetTypeFromProgID( progID, true );
//2. Na jego podstawie tworzymy instancje
return Activator.CreateInstance( _COMType );
} |
Jak widzimy, argumentem tej funkcji jest identyfikator komponentu COM+ (można go odczytać w Component Services Explorer). Na jego podstawie pobieramy typ tworzonego komponentu a na końcu tworzymy instancje, którą zwracamy do konstruktora.
[Kod C#]
public COMWrapper ( string progID )
{
try
{
this._COM = CreateCOM( progID );
}
catch ( COMException ex )
{
throw ex;
}
} |
Teraz, kiedy juz wiemy jak stworzyć obiekt COM+, możemy przejść dalej, czyli do zaimplementowania metody odpowiedzialnej za wywołanie funkcji COM+.
Pierwszą czynnością, jaką musimy wykonać przy próbie wywołania metody jest sprawdzenie czy ona istnieje. Przedstawię dwie możliwości realizacji tego celu.
Jednym ze sposobów wykonania tego zadania jest wykorzystanie metody z przestrzeni nazw System.Reflection GetMethod(). Za jej pomocą możemy nie tylko sprawdzić czy dana funkcja istnieje w obiekcie COM ale także zwrócić jej instancje w postaci obiektu klasy MethodInfo, dzięki czemu możemy zdobyć wiele informacji na jej temat: typy argumentów, ich ilość, nazwa, itp. oraz wywołać metodą Method.Invoke.
[Kod C#]
public bool IsMethodExist( string methodName, Type[] formalArgs )
{
_Method = _COMType.GetMethod( methodName, formalArgs );
return ( _Method == null ) ? false : true;
}
public bool IsMethodExist( string methodName )
{
_Method = this.COMType.GetMethod( methodName );
_Member = _COMType.GetMember( methodName, BindingFlags.DeclaredOnly );
return ( _Method == null ) ? false : true;
} |
Metody można zmodyfikować by od razu zwracały nam obiekt typu MethodInfo, gdy znajdą żądaną metodę, lub null w przeciwnym przypadku. Mnie w powyższym przykładzie chodziło jedynie od zademonstrowanie możliwości, jakie oferuje nam środowisko. Takie metody są zamieszczane w przykładowej aplikacji demonstrującej działanie omawianych przeze mnie mechanizmów.
Drugim sposobem jest wykorzystanie funkcji findMember i podanie odpowiednich filtrów.
[Kod C#]
public bool IsMethodExist( string methodName )
{
try
{
//Znajdź wszytkie metody o podanej nazwie
_Members = this._COMType.FindMembers
(
MemberTypes.All,
BindingFlags.Default,
new MemberFilter(DelegateToSearchCriteria),
methodName
);
}
catch ( Exception ex )
{
Debug.Write( ex.Message.ToString() );
throw ex;
}
return ( (_Members.Length == 0) ) ? false : true;
}
public static bool DelegateToSearchCriteria(MemberInfo objMemberInfo,
Object objSearch)
{
// Porównanie nazwy aktualnej metody z naszymi kryteriami
if( objMemberInfo.Name.ToString() == objSearch.ToString() )
return true;
else
return false;
} |
W tym przykładzie zostaje wskrzeszony delegat, który jest przekazywany jako argument metody FindMembers. Ma to na celu określenie argumentów porównania. Metoda podana jako argument delegata jest wywoływana dla każdego członka obiektu dla którego wywołano metodę FindMembers i dokonywane jest porównanie atrybutu aktualnego obiektu, z podanym przez nas co prezentuje funkcja DelegateToSearchCriteria(MemberInfo objMemberInfo, Object objSearch).
Kolejnym krokiem będzie zaimplementowanie metody służącej do wywołania funkcji komponentu. Platforma .NET oferuje dwie metody (Type.InvokeMemeber, MethodInfo.Invoke), za pomocą których możemy wywołać funkcje COM+. Ich użycie prezentuje poniższy fragment kodu:
[Kod C#]
public object InvokeMethod(string methodName, object[] args)
{
object res = null;
if ( IsMethodExist( methodName ) )
{
try
{
res = _COMType.InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
_COM,
args
);
}
catch(Exception ex)
{
throw ex;
}
}
return res;
}
public object InvokeMethod( string methodName, object[] args, Type[] formalArgs)
{
object res = null;
if ( IsMethodExists( methodName, formalArgs ) )
{
_Method = GetMethod( methodName, formalArgs );
try
{
_Method.Invoke( _COM, args );
}
catch(Exception ex)
{
throw ex;
}
}
return res;
} |
Można dodatkowo kryteria wyszukiwania metody zawęzić do listy argumentów formalnych, gdy wiemy, że w obiekcie są metody przeciążone. Dlatego druga metoda ma dodatkowy parametr o nazwie formalArgs. Pozwoli to na pobranie żądanej metody i uniknięcia błędów wywoływaniu.
Gdy już wiemy jak powołać do życia obiekt COM+ i zaimplementowaliśmy metody związane z jego obsługą warto się zastanowić jak bezpiecznie zwolnić zasoby po utworzonym obiekcie. Przedstawię sposób realizacji tego celu. Kod realizujący zwalniane zasobów zamieszczam poniżej:
[Kod C#]
/// <summary>
/// Bezpieczne usuwanie obiektu COM+
/// </summary>
private void Release()
{
if( null == this.COM )
return;
Marshal.ReleaseComObject( this._COM );
this._COM = null;
Debug.WriteLine( “COM released successfully” );
} |
Poniżej są zdefiniowane: metoda Dispose i destruktor klasy, wywołujące poprzednio zdefiniowaną metodę.
[Kod C#]
protected void Dispose ( bool disposing )
{
if ( !_IsDisposed )
{
if ( disposing )
{
}
this.Release();
}
_IsDisposed = true;
}
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
~COMWrapper ()
{
Dispose( false );
} |
By dowiedzieć się więcej odnośnie zarządzania pamięcią na platformie .NET odsyłam do artykułu [9].
Gdy już zaimplementowaliśmy klasę, która będzie obsługiwać żądany obiekt COM+ stwórzmy aplikację demonstrującą mechanizm „Late binding”.
Wyobraźmy sobie sytuację, że otrzymano zlecenie napisania aplikacji do utrzymywania istniejącej aplikacji, wdrożonej na serwer produkcyjny, ale jeszcze prace nad nią trwają. Głównym zadaniem tej aplikacji będzie wczytanie pliku konfiguracyjnego, gdzie będą zdefiniowane komponenty COM+, których metody będzie wywoływał użytkownik. Dodatkowo obiekt COM wybieramy z menu a aplikacja ma wyświetlić metody, które można wywołać, następnie wygeneruje formularz do podania argumentów, wywoła i wyświetli podsumowanie z wynikiem wywołania. Do tego celu stworzona zostanie kontrolka.
Zawartość pliku konfiguracyjnego dla obiektu będzie wyglądała następująco:
<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="http://tempuri.org/Config.xsd">
<COMMessangerConfiguration>
<COMObject name="prjMTSDebug._clsMTSDebug">
<Interface name="IMath">
<Method name="Class_Sum">
<Arg type="int" name="Val1" />
<Arg type="int" name="Val2" />
</Method>
<Method name="Class_Sub">
<Arg type="int" name="Val1" />
<Arg type="int" name="Val2" />
</Method>
<Method name="Class_Mul">
<Arg type="int" name="Val1" />
<Arg type="int" name="Val2" />
</Method>
<Method name="Class_Div">
<Arg type="int" name="Val1" />
<Arg type="int" name="Val2" />
</Method>
</Interface>
</COMObject>
</COMMessangerConfiguration>
</Configuration>
Do wczytania pliku konfiguracyjnego zdefininiumy odpowiednie klasy (ich nagłówki możemy wygenerować poleceniem xsd z Visual Studio 2003 Command Prompt) odzwierciedlające strukturę tego pliku. Do wypełnienia danym korzystam z mechanizmu serializacji. Po szczegóły odsyłam do załączonego kodu źródłowego i do artykułu [10].
Gdy już mamy kontrolkę też pozostaje nam stworzyć fasadę pomiędzy kontrolką a naszą klasa COMWrapper. Wyposażę ją w dwie metody Invoke o różnych parametrach w zależności od tego czy chcę wyszukiwać na podstawie parametrów formalnych czy tylko na podstawie nazwy.
Nasza klasa będzie wyglądała następująco:
[Kod C#]
public class COMWrapperFactory
{
public COMWrapperFactory(){}
public static string InvokeCOMMethod(
string progID,
string methodName,
object[] args
)
public static string InvokeCOMMethod(
string progID,
string methodName,
object[] args,
Type[] formalArgs
)
} |
By nie zasypać dużą ilością kodu zamieszczę jedną z metod Invoke, gdyż jedyną różnicą między mini jest to, iż wywołują inną metodę klasy COMWrapper.
[Kod C#]
public static string InvokeCOMMethod(
string progID,
string methodName,
object[] args,
Type[] formalArgs
)
{
try
{
using (NOSCOMWrapper wrapper = new NOSCOMWrapper(progID))
{
return wrapper.InvokeMethod(methodName,args,formalArgs);
}//using
}//try
catch(Exception ex)
{
throw ex;
}//catch
}//InvokeCOMMethod |
Teraz pora na testowanie. Po uruchomienie aplikacji LateBinidingTest powinna pojawić się przedstawiona poniżej tabelka:

rys.2. Wybieranie metody do wywołania
Rozwijana lista jest wypełniona funkcjami jakie udostępnia nasz komponent. Można przykładową aplikację wzbogacić o menu, które dzięki przekształceniom xslt pliku konfiguracyjnego umożliwi obiektu COM, którego funkcję chcemy wywołać Po wybraniu metody pojawi się formularz służący do podania argumentów, a po wywołaniu ujrzymy komunikat o sukcesie wraz z wynikiem lub o błędzie wywołania. Po wybraniu metody ujrzymy tabelką podobną do tej poniżej.

rys.3. Formularz do wprowadzenie argumentów
Po wybraniu metody Class_Sum i podaniu argumentów 2 i 3 ujrzymy taki komunikat:

rys.4. Podsumowanie wywołania
Jak więc widzimy nasza aplikacją działa zgonie z założeniem.
Opisane powyżej mechanizmy dostępu do COM+ nie są pozbawione wad. Pierwszy mechanizm był zależny od wersji, drugi natomiast pozbawia nas sprawdzenia zgodności typów na etapie kompilacji. Ale znów nie jesteśmy bezradni i z pomocą przychodzi nam środowisko .NET a dokładniej mówiąc klasy RealProxy, która również umożliwia nam dostęp do COM+ i znacznie zwiększa nasze możliwości, a przede wszystkim umożliwia wywoływanie metod za pomocą mechanizmu „LateBinidins” oraz nie pozbawia nas cech mocno typizowanego języka. Dzięki temu możemy korzystać z wybranych metod danego komponentu niezależnie od jego wersji, a tym samym nie musimy usuwać i dodawać referencji do komponentu w naszym projekcie.
"RealProxy"
Przestrzeń nazw System.Runtime.Proxies zawiera abstrakcyjną klasę RealProxy. Tej klasy możemy używać do stworzenia własnej klasy proxy dla dowolnej klasy, ponadto umożliwia przechwytywanie wywołania metod poprzez przeciążenie metody Invoke klasy RealProxy. Dzięki temu można przechwycić wywołanie danej metody, a zamiast niej wywołać inną. Kiedy wywołujemy metodę RealProxy.Invoke przechwycenie wywołania odbywa się przez klasę TransparentProxy. Jest ona tworzona dla konkretnego obiektu w trybie runtime. Klasa ta jest jak gdyby ukryta. Jej instancje tworzymy poprzez wywołanie metody RealProxy.GetTransparentProxy() i właśnie dzięki temu możemy zrealizować przechwycenie wywołania metody.
Przechwytywanie wywołania metody odbywa się przez klasę TransparentProxy, która jest tworzona dla konkretnego typu w trybie runtime. Instancję klasy TransparentProxy możemy utworzyć poprzez wywołanie metody RealProxy.GetTransparentProxy() i właśnie dzięki temu możemy zrealizować przechwycenie wywołania metody implementując metodę Invoke naszej klasy Proxy.
Żeby zademonstrować tworzenie klasy Proxy stwórzmy przykładową aplikację w celu oswojenia się z nowymi informacjami, a poniżej móc łatwo stworzyć klasę proxy dla naszego komponentu.
Na początek stworzymy prosty interfejs, który będzie implementowała klasa, dla której stworzymy proxy.
Powiedzmy ze nasza klasa będzie posiadała jedną metodę, której zadaniem będzie „powiedzenie” „Hello”, oto jej interfejs.
[Kod C#]
public interface IHello
{
string SayHello();
} |
A to juz sama klasa:
[Kod C#]
public class Hello : IHello
{
public Hello()
{
}
public string SayHello()
{
return “Hello”;
}
} |
Teraz pora na napisanie klasy proxy. Jak Abyśmy mogli utworzyć klasę proxy dla danej klasy (czy komponentu), musimy dziedziczyć po klasie abstrakcyjnej RealProxy i zaimplementować w niej metodę Invoke.
Proxy dla klasy Hello wygląda następująco:
[Kod C#]
public class HelloProxy : RealProxy
{
private IHello _ActuHello;
public static IHello Create()
{
Hello hello = new Hello();
HelloProxy proxy = new HelloProxy( hello );
return proxy.GetTransparentProxy() as IHello;
}
public HelloProxy( IHello obj ) : base( typeof( Ihello ) )
{
_ActuHello = obj;
}
public override IMessage Invoke( IMessage msg )
{
object returnValue;
ReturnMessage retMsg;
Debug.WriteLine( “Method was intercepted...” );
IMethodCallMessage callMsg = msg as IMethodCallMessage;
returnValue = actuHello.SayHello();
retMsg = new ReturnMessage( returnValue,
null, 0,callMsg.LogicalCallContext,
callMsg );
return retMsg;
}
} |
Klasa ta nie jest zbyt skomplikowana, ale kilka spraw wymaga wyjaśnienia. Interfejs stworzyliśmy w celu zwracania go przez klasę proxy by w nim definiować, jakie metody chcemy wywoływać. Interfejs IMethodCallMessage jest generowany jako wynik metody wywoływanej przez klasę proxy na zadanym obiekcie i jest używany do transportowania danych do strony serwera. Przekazuje on też kontekst wywołania metody. ReturnMessage, przechowuje komunikat zwracany przez wywołaną metodę.
Teraz już wiemy jak działa klasa Proxy, więc możemy zaimplementować ją dla komponentu COM+, co umożliwi na korzystanie z mechanizmu „Late binding”. Ponadto uniezależnimy się od wersji komponentu, gdyż nie trzeba będzie dodawać referencji do COM+. Dzięki stworzenie interfejsu dla naszego obiektu COM+ będziemy mogli korzystać z udogodnień, jakie daje nam mocno typizowany język C#. Pierwszą czynnością, jaką musimy wykonać jest stworzenie interfejsu dla naszego komponentu:
[Kod C#]
interface IMath : IDisposable
{
short Class_Sum(short Par1, short Par2);
short Class_Sub(short Par1, short Par2);
short Class_Mul(short Par1, short Par2);
short Class_Div(short Par1, short Par2);
} |
Teraz należy połączyć wiedzę zdobytą w poprzednim punkcie, gdzie omawiałem mechanizm „Late binding” oraz z początku tego punktu i napisać klasę proxy dla naszego komponentu. Nagłówek klasy ze zmiennymi i nagłówkami metod znajduje się poniżej.
[Kod C#]
public class COMProxy : RealProxy
{
/// Obiekt COM+, którego metody będziemy wywoływać
public object _COM;
/// <summary>
/// W tej aplikacji będziemy korzystać z mechanizmy late binidings
/// I podobnie jak w poprzednim przykładzie obiket COM będzeimy
/// tworzyć na podstawie Prog ID zamiast of CLSID. W ten sposób
/// jesteśmy niazależni od wersji
/// </summary>
/// <param name="progID">Prog ID</param>
/// <returns></returns>
private static object CreateCOM( string progID )
/// <summary>
/// Tworzymy TransparentProxy i zwracamy jego isntancje ale jako
/// IDisposable
/// </summary>
/// <param name="progID">Prog ID</param>
/// <param name="interfaceType">Typ obiektu COM+, który
/// tworzymy</param>
/// <returns></returns>
public static IDisposable Create( string progID, Type interfaceType )
public COMProxy( object theCOM, Type interfaceType )
:base( interfaceType )
/// Metoda służąca do przechwytywania wywołania metod:
public override IMessage Invoke(IMessage myMessage)
/// <summary>
/// Bezpieczne usuwanie COM+
/// </summary>
private void Release()
~COMProxy()
} |
Tak wygląda nasza klasa, nadszedł czas, aby zapełnić nasz szablon kodem. By być konsekwentnym zacznę implementację od utworzenia i zwrócenia obiektu TransparentProxy dla COM+.
[Kod C#]
public static IDisposable Create( string progID, Type interfaceType )
{
object theCOM = CreateCOM( progID );
COMProxy wrapper = new COMProxy( theCOM, interfaceType );
return wrapper.GetTransparentProxy() as IDisposable;
} |
Dla czytelności i łatwiejszego zrozumienia omawianego mechanizmu zamieszczamy kod konstruktora (który tylko zapisuje referencje do powoływanego obiektu COM), który jest niejawnie wywoływany w metodzie Create. Taki scenariusz jest wygodny przy korzystaniu z „Late biniding” i zwracaniu obiektu TransparentProxy. Dzięki takiemu rozwiązaniu wywołujemy metodę Create i automatycznie uzyskujemy to co nas interesuje, czyli obiekt proxy.
[Kod C#]
public COMProxy( object theCOM, Type interfaceType )
:base( interfaceType )
{
this.COM = theCOM;
}
public override IMessage Invoke(IMessage myMessage)
{
object returnValue = null;
if (myMessage is IMethodCallMessage)
{
IMethodCallMessage callMessage = myMessage as IMethodCallMessage;
//sprawdzamy czy argument jaki dostaliśmy to faktycznie obiekt
//klasy MethodInfo jeśli tak idziemy dalej jeśli nie wychodzimy
if( callMessage.MethodBase is MethodInfo )
{
//pobieramy z interfejsu Imessage oinstacje wywoływanej metody
MethodInfo method = callMessage.MethodBase as MethodInfo;
//sprawdzenie czy wywoływana metoda to Dispose jeśli tak
wywołujemy metode Realease I zwalniamy zasoby
if( method.Name.CompareTo("Dispose") == 0 )
{
this.Release();
}
//wpp wywołujemy metodę
else
{
object invokeObject = this.COM;
Type invokeType = this.COM.GetType(); returnValue = invokeType.InvokeMember
(
method.Name, BindingFlags.InvokeMethod,
null, invokeObject,
callMessage.Args
);
}
//kontruujemy komunikat zwracany.
ReturnMessage returnMessage
= new ReturnMessage
(
returnValue, null, 0, callMessage.LogicalCallContext,
callMessage
);
return returnMessage;
}
else
{
Debug.WriteLine( "Unrecognized Invoke call" );
}
}
return null;
} |
Strona do testowania wygląda identycznie jak strona z punktu “Early bindings”. Jedyną różnica jest obsługa zdarzenia po naciśnięciu przycisku „Wynik”, której kod zamieszczamy poniżej. Dla lepszej czytelności pomijamy nieistotne fragmenty kodu:
[Kod C#]
switch(operation)
{
case Operations.Addition:
using( IMath math = (IMath)COMProxy.Create
(
"prjMTSDebug.clsMTSDebug",
typeof( IMath )
)
)
{
res = math.Class_Sum( par1, par2);
}
break;
case Operations.Subtraction:
using( IMath math = (IMath)COMProxy.Create
(
"prjMTSDebug.clsMTSDebug",
typeof( IMath )
)
)
{ res = math.Class_Sub( par1, par2);
}
break;
case Operations.Multiplication:
using( IMath math = (IMath)COMProxy.Create
(
"prjMTSDebug.clsMTSDebug",
typeof( IMath )
)
)
{
res = math.Class_Mul( par1, par2);
}
break;
case Operations.Division:
using( IMath math = (IMath)COMProxy.Create
(
"prjMTSDebug.clsMTSDebug",
typeof( IMath )
)
)
{
res = math.Class_Div( par1, par2);
}
break;
default:
ErrorMessage = "Wybierz działanie z listy"; break;
}
tbRes.Text = res.ToString();
|
Jak łatwo możemy zauważyć poza tworzeniem obiektu COM wywoływanie metod niczym nie różni od tego z przykładu pierwszego. Co prawda musieliśmy napisać znacznie większą ilości kodu, ale za to zyskaliśmy dużo. Dzięki takiemu rozwiązaniu jesteśmy niezależni od wersji komponentu i nie musimy na przemian dodawać i usuwać referencji do COM+ w projekcie. Ponadto zasoby po obiekcie COM Zwalniamy zaraz po opuszczeniu bloku using (wywołanie metody Release naszej klasy proxy) a nie jak to miało miejsce w pierwszym przykładzie w momencie wystąpienia wyjątku. Dzięki zdefiniowaniu destruktora zasoby po referencji do COM są zwalniane na dwa sposoby:
O poprzez metodę Dispose, której wywołanie wychwytujemy w metodzie Invoke a następnie wywołujemy metodę Release
O Poprzez destruktor w momencie wywołania przez GC metody Finalize w przypadku kiedy zapomnimy o wywołaniu metody Dispose.
Z kolei w przeciwieństwie do „Late biniding” zyskuje się przewagę, iż sprawdzenie zgodności typów jest dokonywane na etapie kompilacji a nie czasie w czasie działania programu, co uwalnia programistę od pisania dodatkowych mechanizmów sprawdzających zgodność typów. Z drugiej strony jest to mechanizm tak elastyczny, gdyż przy dodaniu nowego komponentu COM+ należy stworzyć dla niego interfejs i skompilować źródła.
Przedstawiona klasa COMProxy jest klasa, którą można wykorzystać w projekcie dla wiele różnych komponentów COM+. Żeby tego dokonać musimy wykonać następujące czynności:
O tworzymy interfejs dla tego komponentu i ewentualne potrzebnych typów wyliczeniowych,
O tworzymy referencję do COM+ za pomocą klasy COMProxy,
O używamy jej zawsze w bloku using.
Podsumowanie
Platforma .NET daje programiście dużą swobodę uzyskania dostępu do COM+. Dlatego musimy się dobrze zastanowić, z jakiego mechanizmu będziemy korzystać, gdyż odpowiedni wybór wcale nie jest łatwy. Wszystko zależy od tego, na czym nam zależy: bezpieczeństwie, niezależności od wersji, czy też szybkości działania aplikacji. W tym artykule starałem się pokazać, w jaki sposób można uzyskać dostęp do komponentu COM+ w różnych sytuacjach.
Literatura:
[1] Tworzenie aplikacji COM+
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cossdk/html/32828d4d-aa98-4e6a-b7de-2ec832024517.asp
[2] Jak napisać swoją pierwszą aplikację COM+ przy wykorzystaniu .NET Enterprise Services
http://www.codeguru.pl/Default.aspx?Page=Articles/Details&pubid=260
[3] Mechanizmy odzwierciedlenia w platformie .Net
http://www.codeguru.pl/Default.aspx?Page=Articles/Details&pubid=482
[4] Co to są obiekty proxy.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msaa/msaaccgd_4mlv.asp
[5] Opis Type Library Importer
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptools/html/cpgrftypelibraryimportertlbimpexe.asp
[6] Derek Beyer “C# COM+ Programming”
[7] Łukasz Żarczyński „Refleksja typów w Microsoft.NET”
http://www.codeguru.pl/Default.aspx?Page=Articles/Details&pubid=9
[8] Understanding Classic COM Interoperability With .NET Applications
http://www.codeproject.com/dotnet/cominterop.asp
[9] Tomasz Porosiński “ Nikt nie lubi sprzątać... (I) - Zarządzanie pamięcią na platformie .Net” http://www.codeguru.pl/Default.aspx?Page=Articles/Details&pubid=395
[10] James Johnson „.NET XML Serialization - a settings class” http://www.codeproject.com/soap/xmlsettings.asp
Załączniki: