Sunteți pe pagina 1din 29

13

Interoperation este procesul de interactiune cu codul unmanaged din cadrul unui assembly .NET (cod managed). Este nevoie de interoperation pentru a putea comunica / folosi functionalitati legacy, ce urmaresc modelul vechi de programare COM (ex. suita MS Office) sau sunt pur si simplu scrise in cod unmanaged (C/C++).

Component Object Model (COM) este o interfata ce permite comunicarea interprocese si crearea dinamica de obiecte in orice limbaj de programare care suporta aceasta tehnologie. In esenta COM este o modalitate independenta de limbaj de implementare a unor obiecte astfel incat ele sa poata fi refolosite in medii diferite de cele in care au fost scrise. COM permite reutilizarea obiectelor fara a sti ce contin acestea.

.NET Framework ofera suport pentru interoperabilitatea COM, prin Runtime Callable Wrapper (RCW). RCW este un proxy ce administreaza comunicarea dintre o componenta .NET si una COM, realizand transferul tipurilor de date (marshalling), tratarea eventurilor si gestiunea interfetelor.

In .NET framework un obiect COM trebuie sa fie inregistrat si importat pentru a putea fi folosit. Sa prespupunem ca avem obiectul COM numit librarie.dll. Inregistrarea se face din linia de comanda, folosind comanda: Regsvr32 librarie.dll Dupa inregistrare, obiectul trebuie importat in cadrul proiectului folosind Visual Studio (add Reference) sau utilitarul Type Library Importer (TlbImp.exe).

Deschideti Visual Studio command prompt. Navigati la DLL-ul pe care doriti sa il importati. Scrieti tlbimp <numeDLL>.dll. Acest apel va crea un assembly .NET cu numele <numeDLL>.dll. Daca doriti sa schimbati numele puteti folosi apelul: tlbimp <numeDLL>.dll /out: <numeNouDLL>.dll In continuare importati noul assembly ca o componenta .NET (Add Reference )

TlbImp.exe> creaza assembly-uri .NET din obiecte COM TlbExp.exe> creaza obiecte COM din assemblyuri .NET Regedit.exe : management-ul componetelor inregistrare Ildasm.exe> IL Dissasembler, viewer pt. cod IL Regasm.exe> Assembly Registration Tool : permite adaugarea / scoaterea de assembly-uri .NET din GAC

Sa presupunem ca am adauga la referinte un obiect COM numit: Adobe Acrobat Reader 7.0 Document Browser si l-am instantiat. Folosirea lui e foarte simpla :
axAcroPDF1.LoadFile(fisier.pdf); axAcroPDF1.Print();

In .NET, toate tipurile de exceptii deriva din System.Exception, deci este de ajuns un bloc try{} catch(Exception ex) {} pentru a prinde toate exceptiile din acel bloc. Acest lucru se aplica insa numai pentru exceptiile compatibile CLS ( Common Language Specification ) Exceptiile aruncate in interiorul componentelor COM nu sunt compatibile CLS, iar in .NET Framework 1.1 nu puteau fi prinse de un astfel de bloc try/catch Pentru a rezolva aceasta problema, .NET Framework 2.0 a introdus RuntimeWrapperException, un wrapper pentru orice tip de exceptie incompatibila CLS. Atunci cand este aruncata o exceptie intr-un obiect COM, .NET Framework creeaza o RuntimeWrapperException pentru care seteaza proprietatea WrappedException cu exceptia aruncata in COM, si ridica apoi aceasta exceptie in codul apelant.

Componentele COM nu suporta


Obiecte statice (C#) sau shared (VB.NET). Constructori cu parametri Supraincarcare Mostenirea devine inutila in unele cazuri Portabilitate (sistemele de operare fara registry nu pot sa foloseasca obiecte COM).

OBS:
Parametrii nu pot fi transmisi decat prin referinta Atunci cand un parametru nu are nicio valoare va fi transmis Type.Missing

Am vazut cum putem consuma componente COM in cadrul unei aplicatii .NET, prin intermediul RCW. Este posibila si varianta inversa, cand comunicatiile sunt delegate de COM Callable Wrapper (CCW).

Pentru a crea o componenta compatibila COM puteti urma urmatorii pasi:


Pas1> Creati un .NET class library. Pas2> Deschideti dialog-ul Project Properties. Pas3> Dati click pe tab-ul Build. Pas4> Selectati optiunea Register For COM Interop Pas5> Faceti un build la assembly

Puteti ascunde metode din .NET class library COMului folosind atributul ComVisible, atat la nivel de assembly, cat si la nivelul clasei / metodelor / proprietatilor.

Pentru ca libraria voastra sa fie compatibila COM, trebuie ca urmatoarele cerinte sa fie indeplinite:
Toate clasele trebuie sa aiba un constructor fara parametri Toate tipurile expuse COM trebuie sa fie publice Toti membrii expusi COM trebuie sa fie publici Clasele abstracte nu vor putea fi consumate

Dupa ce va asigurati ca respectati aceste criterii puteti construi componenta COM folosind Visual Studio sau utilitarul TlbExp.

Pasi pentru deployment:


Compilare:
Csc /t:library LibrarieVizibilaCom.cs

Creare fisier TLB:


tlb LibrarieVizibilaCom.dll /out: LibrarieVizibilaComLib.tlb

Creati un script de resurse ClasaVisibilaCom.res in care adaugati IDR_TYPELIB1 typelib LibrarieVizibilaComLib.tlb Recompilati assembly-ul cu noul script resursa adaugat:
Csc /t:library LibrarieVizibilaCom.cs /win32res: LibrarieVizibilaCom.res

.NET framework are posibilitatea de a folosi functii din DLL-uri unmanaged (deci nici componenta COM, nici .NET). Puteti astfel sa scrieti o functie in C++/Assembler si sa o folositi foarte simplu in o aplicatie .NET. Un posibil avantaj este viteza. Multi algoritmi se pot executa mult mai repede in cod unmanged decat in cod managed. Dezavantaje ar fi durata mare a operatiei de conversie dintre tipuri managed / unmanaged si posibilitatea aparitiei memory leak-urilor.

Cu Platform Invoke (P/Invoke) puteti apela metode din dll-uri unamanged (Windows API). Pentru a putea folosi P/Invoke trebuie sa:
Creati o metoda static extern cu signatura functiei pe care doriti sa o apelati Decorati metoda cu atributul DllImport, specificand ca parametru numele dll-ului din care importati functia. Dll-ul trebuie sa fie inregistrat ! Apelati aceasta metoda in codul vostru.

In general e bine sa construiti clase wrapper care sa incapsuleze functionalitatile apelate dintr-un DLL unmanaged. Avantajele unei clase wrapper ar fi:
Transparenta : Cei ce folosesc clasa nu vad nici o diferenta intre apelul de cod managed si apelul de cod unmanaged. Dezvoltatorii nu trebuie sa isi mai aminteasca numele si detaliile din codul unmanaged. Mai putine erori. Apelurile la P/Invoke sunt foarte sensibile la erori in parametri deci e bine sa faceti conversia o singura data bine si apoi sa refolositi.

Multe conversii de tipuri de date din managed in unmanaged se fac automat de catre runtime (ex: int, char, char*). Atunci cand conversia se poate face in mai multe tipuri se poate folosi atributul MarshalAs:
Poate fi aplicat unei proprietati sau unui parametru Primeste ca parametru tipul de date in care se converteste. Acesta este un membru al enumerarii UnmanagedType.

In general, codul unmanaged se asteapta ca o structura primita ca parametru sa aiba membrii aranjati intr-o anumita ordine. In .NET framework runtime-ul poate optimizeaza aranjarea membrilor unui tip la creearea acestuia, astfel incat aranjarea membrilor in cod poate sa difere de cea care ar fi transmisa la un apel de functie unmanaged. Putem defini manual modul de aranjare a membrilor unui tip prin atributul StructLayout.

Cea mai importanta proprietate din StructLayout este LayoutKind. Aceasta se poate specifica in constructor si specifica modul de aranjare a membrilor tipului:
LayoutKind.Auto: Runtime-ul va aranja membrii structurii asa cum doreste. LayoutKind.Sequential: Runtime-ul va mentine aranjarea din cod a membrilor structurii. LayoutKind.Explicit: Runtime-ul va aranja membrii structurii in ordinea specificata de dezvoltator folosind offset-uri de memorie

typedef struct tagLOGFONT { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; TCHAR lfFaceName[LF_FACESIZE]; } LOGFONT;

[StructLayout(LayoutKind.Sequential)] public class LOGFONT { public const int LF_FACESIZE = 32; public int lfHeight; public int lfWidth; public int lfEscapement; public int lfOrientation; public int lfWeight; public byte lfItalic; public byte lfUnderline; public byte lfStrikeOut; public byte lfCharSet; public byte lfOutPrecision; public byte lfClipPrecision; public byte lfQuality; public byte lfPitchAndFamily; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)] public string lfFaceName; }

LayoutKind.Explicit presupune specificarea offsetului in bytes pentru fiecare membru al structurii

In codul unmanaged functiile callback sunt implementate cu ajutorul pointerilor la functie. In codul managed functiile callback sunt implementate cu ajutor delegatilor. Pentru a trimite un callback la cod unmanaged trebuie deci creat un delegat cu prototipul pointerului la functie.

Performanta: Overhead-ul produs de conversia tipurilor managed in unmanaged si invers poate scadea performanta aplicatiei. Pot sa apara si memory leak-uri. Type safety: Codul unmanaged nu este type safe. Securitatea codului: In codul unmanaged nu exista securitate declarativa sau imperativa si o aplicatie care foloseste cod unmanaged s-ar putea sa nu functioneze intr-un mediu partially trusted. Versionarea: Aplicatiile managed ofera un suport mult mai bun si usor de implementat pentru versionare si integrarea cu versiuni anterioare ale unei aplicatii / componente, decat codul unmanaged.