09 maja 2007
Ręczne ładowanie Assembly
Kilka razy spotkałem się na różnych forach poświęconych programowaniu w .NET z pytaniami dotyczącymi "ręcznego" ładowania Assembly. Przeważnie chodziło o załadowanie takich Assembly, które zawierają w sobie zadany typ lub interfejs. Sprawa wydaje się napozór błacha, ale nie do końca.
Nie będę opisywał w jaki sposób wewnętrznie Framework.NET obsługuje uruchamiane aplikacje, ale w skrócie łopatologicznie można to ująć w następujący sposób. Programy w .NET działają w obrębie domeny aplikacji (AppDomain). (Przy czym z jednej aplikacji można stworzyć wiele domen i to właśnie wykorzystamy.) Do danego AppDomain ładowane są kolejne Assembly (dll'ki) poczynając od ExecutingAssembly, czyli programu, który zapoczątkował uruchomienie aplikacji. Mowa w tym miejscu jest oczywiście o naszym pliku exe. Dalej ładowane są Assembly, z którymi powiązany jest nasz program (referencje).
Wszystko wygląda pięknie, ale niestety Framework nie pozwala na wyładowanie z pamięci raz załadowanego Assembly. Jeśli teraz chcemy załadować wszystkie Assembly z podanego katalogu, a okaże się, że tylko kilka z nich posiada poszukiwany przez nas interfejs to pozostałe będą tylko nie potrzebnie zajmować nam pamięć. Można tego uniknąć tworząc samemu drugą domenę aplikacji, do której załadujemy Assembly z katalogu, zapamiętamy na boku nazwy zgodnych z naszym interfejsem i wyrzucimy pozostałe wyładowując tymczasową domenę.
Interface IPlugin
Projekt zaczniemy od zdefiniowania bardzo prostego interfejsu. Będzie on implementowany przez te Assembly, które będziemy chcieli załadować w naszej aplikacji.
public interface IPlugin
{
string GetName();
void Test();
}
Będzie nam też potrzebna klasa, która zostanie uruchomiona w tymczasowej domenie. Będziemy z nią rozmawiać poprzez Remoting.NET, ponieważ jej kontekst nie będzie należał już do naszej bazowej aplikacji. Tak jakby była to inna, zewnętrzna aplikacja. Klasę tą nazwałem RemoteLoader. Dodatkowo musi ona dziedziczyć po obiekcie MarshalByRefObject, aby można było odwołać się do niej poprzez Remoting. Tak naprawdę jest ona dosyć prosta, bo implementuje tylko jedną metodę GetPlugins. Metoda ma za zadanie znaleźć wszystkie assembly z określonego katalogu i podkatalogów, które implementują interface IPlugin oraz klasy implementujące ten interface nie są klasami typu Abstract.
Do powołania kolejnych Assembly znalezionych w katalogu użyłem metody ReflectionOnlyLoadFrom(filename), która ładuje tylko kontekst potrzebny do operacji poprzez Reflection, a takie tylko są nam potrzebne. Tworząc obiekt Assembly tą metodą nie możemy powołać żadnego obiektu z tego Assembly, ale dla nas jest to wystarczające. Dalej to już tylko sprawdzenie każdej klasy z warunkami, które opisałem powyżej. Pasujące pliki Assembly zapisujemy na boku i zwracamy w postaci tablicy stringów.
Utworzenie tymczasowej domeny aplikacji
Utworzenie tymczasowej domeny aplikacji wymaga zdefiniowania dwóch obiektów.
- AppDomainSetup - obiekt definiuje podstawowe parametry domeny, takie jak ApplicationName czy katalog, gdzie kopiowane są ładowane Assembly w ramach domeny;
- Evidence - obiekt definiujący zasady bezpieczeństwa (security policy).
Do utworzenia domeny skorzystałem z metody statycznej AppDomain.CreateDomain(String, Evidence, AppDomainSetup) przyjmującej trzy parametry, czyli własną nazwę domeny oraz te dwa obiekty, o którym jest mowa powyżej.
W tym momencie już możemy odwołać się do naszej metody GetPlugins. W tym celu tworzymy lokalnie obiekt RemoteLoader, który będzie stanowił proxy pomiędzy domenami. Proxy te musi implementować ten sam interface. Utworzenie proxy wykonujemy wykonując metodę CreateInstanceAndUnwrap na obiekcie reprezentującym tymczasową domenę. Metoda ta jest uruchamiana z dwoma parametrami. Pierwszy podaje pełną nazwę Assembly, które ma zostać załadowane jako główne Assembly w domenie tymczasowej. Drugi parametr to pełna nazwa (wraz z namespace) obiektu, który ma być powołany w tymczasowej domenie.
Wykonanie metody GetPlugins na naszym proxy jest już teraz możliwe. W przykładzie, który udostępniłem na swojej stronie jest to akurat jeden i ten sam Assembly. Działanie programu można prześledzić w ProcessExplorerze, gdzie dokładnie widać istnienie dwóch domen aplikacji.

Na koniec, gdy już mamy pobraną listę plików (Assembly) spełniających nasze warunki możemy wyładować tymczasową domenę. ProcessExplorer pokaże wtedy, że w kontekście naszej aplikacji istnieje już tylko jedna domena.

Podsumowanie
Pokazany przykład pozwala na zbadanie jakie typy zawarte są w Assembly, które są zgromadzone w wybranym katalogu. Dzięki temu, że cała operacja odbywa się w oddzielnej domenie aplikacji w każdej chwili możemy się pozbyć załadowanych Assembly z pamięci wyładowując tymczasową domenę.
Zapraszam do pobrania małego programiku obrazującego ręczne ładowanie Assembly z określonymi warunkami.
Powiadamianie o komentarzach
Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj
Subskrybuj komentarze za pomocą
Comment Policy: No HTML allowed. URIs and line breaks are converted automatically. Your e–mail address will not show up on any public page.