Small place on earth

”Imagination is more important than knowledge” - Albert Einstein
Plugins şi System.Reflection.Assembly

În sfârşit am găsit timp pentru a explica întrebarea de mai devreme: La ce este folositoare clasa Assembly.

Au fost mai multe răspunsuri la respectiva întrebare. Cel pe care îl urmăream eu era că această clasă este folosită pentru încărca plugin-uri într-o aplicaţie. Când am pus acea întrebare trebuie să recunosc că mă refeream strict la plugin-uri. Totuşi, am descoperit mai multe moduri în care se poate folosi această clasă.

Un Assembly este unitate de bază din care sunt alcătuite aplicaţiile .NET. Un Assembly conţine, pe lângă codul MSIL, şi o serie de alte informaţii care descriu codul conţinut. Folosind clasa Assembly se pot determina o serie de informaţii interesante legate de codul conţinut în respectivul fişier. Când spun informaţii interesante mă refer la: tipurile definite, modulele incluse în fişier şi aşa mai departe.

Nu am să prezint toate aceste funcţioanlităţii,  pentru a vedea fiecare funcţionalitate în parte vă recomand să mergeţi pe site-ul MSDN ce tratează această clasă. Totuşi, am să vă spun de metoda ReflectionOnlyLoadFrom, metodă care poate fi utilă în momentul în care se doreşte doar investigarea tipurilor de date dintr-un Assembly, nu şi instanţierea lor - interesant, nu?

Revenind la titlul acestui post şi la legătura acestei clase cu proiectul Blue Spirit. După cum am spus aici şi aici acest proiect a fost gândit să fie modular. Acest lucru înseamnă că trebuia să folosesc plugin-uri. Pentru a folosi încărca plugin-urile, am folosit clasa Assembly pentru a determina ce tipuri sunt definite şi clasa Activator pentru a instanţia respectivele tipuri. Există şi posibilitatea de a folosi funcţia CreateInstance asociată cu clasa Assembly pentru a instanţia un obiect.

Pentru că au fost voci care au cerut să vadă cod Big Smile [:D] am să prezint mai jos codul folosit pentru a încărca plugin-uri în cadrul proiectului meu.

Înainte de a trece la cod, trebuie să fie un disclaimer: codul folosit de mai jos este prezentat aşa cum este şi nu oferă garanţii. Dacă prin rularea lui apar probleme la sistemul pe care a fost rulat, autorul (adică eu Smile [:)]) nu va fi făcut răspunzător pentru acest lucru. Eu folosesc acest cod în cadrul proiectului fără probleme, dar acest disclaimer este "just in case".

Ar mai trebui spus modul în care se implementează plugin-urile. Există o interfaţă care defineşte un comportament pentru respectivul plugin. Ceea ce trebuie să facă cel care crează plugin-ul este să preia interfaţa şi să ofere o implementare a comportamentului.

Codul este prezentat mai jos:

/// <summary>

/// Incarca combobox-urile cu plugin-urile din foldere

/// </summary>

/// <param name="folder">Folder-ul din care incarcam</param>

/// <param name="myCombo">Combobox-ul in care punem</param>

/// <param name="myDataType">Tipul de date care se doreste sa fie incarcat din DLL</param>

private void LoadPlugins(string folder, ComboBox myCombo, Type myDataType)

{

DirectoryInfo myDir = new DirectoryInfo(folder);

FileInfo[] myFiles = myDir.GetFiles("*.dll");

foreach (FileInfo f in myFiles){

   try{

      Assembly myAssembly = Assembly.LoadFile(f.FullName);

      Type[] myClasses = myAssembly.GetTypes();

      foreach (Type t in myClasses)

      {

//Determinam daca t implementeaza interfata ceruta.

Type[] interfaces = t.GetInterfaces();

foreach (Type myInterfaceType in interfaces){

   if (myInterfaceType.Equals(myDataType)){

ObjectHandle myObj = Activator.CreateInstanceFrom(f.FullName, t.ToString());

myCombo.Items.Add(myObj.Unwrap());

            }

         }

      }

   }

   catch (Exception exc){

      System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", this, exc.Message));

   }

}

}

În concluzie, pentru a determina ce conţine un assembly, încărcăm acel fişier într-o instanţă a clasei Assembly şi apelăm metode asupra instanţei pentru a obţine informaţiile necesare.

Sper că v-a fost de folos acest mini-articol.

Technorati tags: ,

Posted: 7 iunie 2006 21:34 by aghiondea
Filed under: ,

Comments

Marius Cristian CONSTANTIN said:

Aici as mai fi bagat sa vad daca tipul e public Type.IsPublic (dupa gust, dar pare un pic mai profesionist, mai ales daca APIul creata ar merge la third-party), si bine inteles niste atribute custom acolo sa decorezi clasa, ca asa sta bine unu plugin, cu descriere si alte informatii folositoare.
O alta problema pe care am mai intalnit-o este unde declari interfata cu myDataType. Adica nu mai merge ca pe vremea C++, unde declarai intefetele la fel in .H si totul mergea magic, aici daca ai interfata IA in assembly-ul A si IA in assembly-ul B equals o sa-ti dea un false de mai mare frumusetea.
O rezolvare destul de eleganta este declararea interfetei chiar in assemblyul gazda (hostul pluginurilor) si se face o referentiere inversa la compilare. Problema care a fost pana la VS2005, era ca nu puteai referentia targeturi .exe la compilare ci trebuia sa le redenumesti de mana in .dll. In 2005 s-a rezolvat finally si problema asta. Altfel o sa ai un assembly doar cu interfetele, cam neplacut dupa parerea mea.
# iunie 8, 2006 00:44

aghiondea said:

Ai dreptate când spui că ar fi trebuit verificat dacă tipul este public. De atributele custom nu am simţit nevoia la acest proiect, dar am să ţin minte pe viitor Wink [;)].
Însă nu înţeleg de ce spui că o interfaţă nu este bine dacă este într-un singur assembly. De ce aş vrea să pun interfaţa alături de alte componente din codul meu? Şi dacă vreau să dau interfaţa unui 3rd-party ca să o implementeze, ce fac? Îi dau şi restul de cod?
Eu cred că e mai ok să fie implementată interfaţa într-un singur loc, loc care să fie referit de cei ce o implementează. Este şi mai simplu de făcut mentenanţa.
Legat de exemplul tău cu cele 2 assembly-uri, de unde ştiu că cele 2 interfeţe chiar reprezintă acelaşi lucru? De unde ştiu că au exact acelaşi comportament... Ar fi ca şi cum ai compara mere cu pere... normal că va da false.
Dacă ai o interfaţă unică care este implementată de două clase diferite (din assembly-uri diferite) atunci ştii sigur că un equals (la nivel de interfaţă) va returna true dacă ambele implementează respectiva interfaţă.
Ai putea să elaborezi un pic ideea cu interfaţa în assembly-ul host?
# iunie 8, 2006 12:55

Marius Cristian CONSTANTIN said:

Sigur ca da, no problem. Ca idee, proiectul meu de diploma a fost bazat tot pe o chestie din asta cu pluginuri, dar era un sistem de achizitie de date si pluginurile erau mai mult drivere care stiau sa discute cu dispozitivele.
Bun asa, sa revenim la ideea unde gazduim interfata si chestiile comune in general (atributele custom, stiu am o idee fixa).
Avem 2 solutii:
- 1. facem un assembly separat, in care se declarar doar interfata, si acest assembly va fi referentiat si de proiectul gazda si de plugin. Prin proiect gazda inteleg proiectul principal care nu stie sa faca prea multe ci doar orchestreaza (incarca pluginurile, le apeleaza metodele, etc). Asta e ideea sa zicem oarecum clasica.
- 2. avand in vedere ca intotdeauna vom avea gazda (nu putem avea pluginuri fara gazda) SI cel mai important, gazda intotdeauna va ajunge la client (altfel nu putem rula pluginuri fara gazda), ce ar fi daca definim interfata noastra in proiectul gazda. Atunci in loc de 2 referinte de la pct. 1, vom ramane cu 1 singura, proiectul pluginul-ui va referentia proiectul gazdei pentru a obtine definitia interfetei. Dupa cum ziceam mai sus pana la 2005, mediul avea o mica problema in sensul ca gazda in general fiind .EXE (la mine asa era, un serviciu Windows), nu puteai sa adaugi referinta direct, ci intai trebuia sa copiezi si apoi sa redenumesti fisierul ca sa fie DLL si abia apoi il puteai adauga.
Sper ca m-am facut inteles destul de bine, altfel mai incerc sa explic.
Legat, de definitia interfetei in cele 2 assembly-uri, asta a fost un fel de "rookie mistake", fiind obisnuit cu C++ la inceputuri, am zis ca merge, ei bine, nu.
Si un lucru demn de mentionat, este ca in strategia 2, desi sistemul este destul de tolerant la recompilari ale gazdei, pentru sanatatea mintala a tuturor este bine sa se aplice doar cand se stie/presupune ca la un moment dat gazda nu va fi recompilata atat de des. Altfel pot apare niste probleme, de depanezi la el de o iei razna.
# iunie 8, 2006 19:38

aghiondea said:

Interesantă soluţia 2, dar probabil că ai urmărit altceva decât mine în proiect. Eu am dorit ca interfaţa pe care o dezvolt să o pot oferi oricui vrea să scrie un plugin (chiar şi codul sursă dacă ar vrea), dar fără să îi dau întreaga aplicaţie (cu atât mai puţin codul sursă Smile [:)]).
Cu prima variantă, cea cu assembly separat, se poate face asta chiar lejer. Se constuieşte o singură dată acel assembly ce conţine interfaţa, şi apoi este folosit în comun de host şi de plugin-uri.
La varianta propusă de tine, mi se pare că este un pic mai dificil pentru un 3rd party să implementeze interfaţa, deşi este mai lejer pentru host Smile [:)]. Oricum, probabil că au fost anumite constrângeri care te-au făcut să implementezi soluţia ta, şi bănuiesc că mai mult tu ai dezvoltat acele plugin-uri (care ascultă dispozitivele), nu?
# iunie 9, 2006 00:28

Marius Cristian CONSTANTIN said:

Pluginurile care au fost scrise de mine au fost mai mult de demo. La modul ideal (adica daca proiectul ar fi avut o finalitate practica si chiar s-ar fi folosit) pluginurile erau scrise de "producatorul" de hardware sau ma rog, de cei interesati, care erau 3rd party.
Acuma trebuie sa precizam pentru a nu induce lumea in eroare, ca si in solutia 2 se poate obtine ceea ce vrei tu, avand in vedere ca in .NET assemblyurile sunt self-descriptive, bazate pe metadate. Adica, in solutia 2 oricine vrea sa scrie un plugin trebuie sa faca urmatoarele: ia hostul (oricum trebuie sa-l ia ca sa poata rula ceva), il adauga ca referinta in proiectul lui, si implementeaza interfata ISuperplugin, interfata pe care o "vede/intelege" ca descriere in urmatoarele moduri:
- cu reflector (normal, incepem cu versiunea cea mai hardcore :) )
- in fisierul .CS care contine definitia interfetei. Diferenta fata de solutia 1, este ca avand doar acest fisier, nu vor putea reobtine recompila proiectul, ceea ce oricum nu era prea recomandat.
- in manualul de utilizare (in general trebuie sa le furnizezi o minima documentatie, cu ce ar trebui sa primeasca/sa intoarca)

Acuma nu vreau sa intelegi ca solutia mea este supertare si ca trebuie neaparat folosita, ca idee, in proiectul meu de diploma a fost implementata solutia 1, solutia 2 am gasit-o intr-un acces de inspiratie/transpiratie, si e implementata intr-un produs comercial, si apoi am modificat proiectul de diploma pentru a folosi solutia 2.
# iunie 9, 2006 02:06

aghiondea said:

Dacă stai bine să te gândeşti, până la urmă, ambele idei merg pe acelaşi drum Big Smile [:D]. Există undeva o interfaţă care este implementată de către plugin-uri şi care este folosită de către host. La cele două soluţii diferă numai locul unde s-au pus interfaţa. La prima soluţie, interfaţa este separată de host, iar la a doua soluţie este integrată în host.
Eu cred că ambele idei sunt ok, depinde de ceea ce vrei să faci. Să-ţi dau un exemplu unde am folosit eu varianta 1, iar varianta 2 nu putea fi folosită: Am avut de făcut un proiect (pentru facultate) în care să simulez o serie de algoritmi de compresie. Pentru că proiectul putea fi făcut în echipă, am cooptat câţiva colegi ca să facem împreună proiectul. Eu m-am oferit să scriu partea de host şi să ofer o interfaţă care să descrie algoritmii, iar ei să implementeze plugin-uri cu algoritmii respectivi. Cum am rezolvat? Am hotărât care sunt metodele care trebuie să fie implementate de către plugin-uri, am scris o interfaţă într-un assembly şi le-am dat acel assembly pentru a implementa plugin-uri. În acelaşi timp, eu am lucrat pe partea de host, folosind aceeaşi interfaţă Smile [:)].
Dar să nu crezi tu că eu spun că ideea mea este cea mai bună Wink [;)] şi că trebuie neapărat folosită. Nu. Singurul răspuns universal care poate fi dat este: Depinde Big Smile [:D] - depinde de proiect, depinde de echipă, depinde de idee, depinde de multe lucruri alegerea soluţiei.
# iunie 9, 2006 08:43

Ioan Bizau said:

-1 pentru varianta 2
Nu stiam ca VS 2005 permite adaugarea de referinte catre fisiere exe, dar mi se pare un lucru anormal. Ce avantaje aduce aceasta complicatie?
# iunie 15, 2006 07:35

Blogging About .NET said:

This post is inspired from another post (in Romanian, sorry for the not Romanian speakers), which...

# februarie 4, 2007 23:25

aghiondea said:

@Ioan: Din câte ştiu, un fişier EXE nu este diferit faţă de un DLL decât printr-un singur flag. Prin urmare, de ce să nu se poată adăuga referinţe spre fişiere de tip EXE? De ce ar fi anormal?

# februarie 22, 2007 00:27
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Enter Code Here: Required

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS