2012-04-24 26 views
8

Buduję aplikację opartą na .NET i chcę umożliwić bardziej rozciągliwy i wtykowy projekt.Zezwalanie na wtyczki C# na haki aplikacji

Dla uproszczenia, wniosek naraża zestaw operacji i zdarzeń:

  • doSomething()
  • DoOtherThing()
  • onError
  • onSuccess

I chciałby zaoferować "wtyczki" do załadowania i podpięcia do niektórych z tych operacji (coś w stylu: Po uruchomieniu Event1 uruchom plugin1).

Na przykład - uruchomić plugin1.HandleError() gdy Zdarzenie onError.

Można to łatwo zrobić z subskrypcji zdarzeń:

this.OnError += new Plugin1().HandleError(); 

Problem polega na tym, że:

  1. Moja aplikacja nie zna typu "Plugin1" (jest to plugin, moim aplikacja nie odnosi się bezpośrednio).
  2. To zrobi instancję wtyczki przed czasem, coś, czego nie chcę, aby chcę zrobić.

W „tradycyjnych” modeli wtyczki, aplikacja („Klient” wtyczek) ładuje i wykonuje kod wtyczki w niektórych kluczowych przykład points.For - aplikacja przetwarzanie obrazu, gdy pewna operacja jest wykonywana).

Kontrola, kiedy należy utworzyć kod wtyczki i kiedy ją wykonać, jest znana aplikacji klienckiej.

W mojej aplikacji wtyczka sama decyduje, kiedy należy ją wykonać ("Wtyczka powinna się zarejestrować w zdarzeniu OnError").

Zachowanie kodu "wykonanie" wtyczki obok kodu "rejestracyjnego" stwarza problem, który DLL wtyczki zostanie załadowany do pamięci w czasie rejestracji, coś, co chcę zapobiec.

Na przykład, jeśli dodaję metodę Register() w bibliotece DLL wtyczki, DLL wtyczki będzie musiał zostać załadowany do pamięci, aby można było wywołać metodę Register.

Co może być dobrym rozwiązaniem dla tego konkretnego problemu?

  • Łagodne ładowanie (lub oferowanie leniwego/gorliwego ładowania) bibliotek DLL wtyczek.
  • Zezwalanie wtyczek na kontrolowanie różnych elementów systemu/aplikacji, do których się podłączają.

Odpowiedz

4

Co należy zrobić, to znaleźć ścieżkę biblioteki dll, a następnie utworzyć z niej obiekt złożenia.Stamtąd trzeba będzie dostać się zajęcia, które chcesz odzyskać (na przykład, coś, który implementuje interfejs):

var assembly = Assembly.Load(AssemblyName.GetAssemblyName(fileFullName)); 
foreach (Type t in assembly.GetTypes()) 
{ 
    if (!typeof(IMyPluginInterface).IsAssignableFrom(t)) continue; 
    var instance = Activator.CreateInstance(t) as IMyPluginInterface; 
    //Voila, you have lazy loaded the plugin dll and hooked the plugin class to your code 
} 

oczywiście stąd jesteś wolny, aby robić, co chcesz, sposoby użytkowania, zapisz się eventy itp.

+1

Wada tej strategii jest bardzo powolna podczas ładowania zespołu. Jest to typowe zagadnienie do refleksji w .Net –

+0

Dzięki już wiem, jak to zrobić. Chciałbym zaprojektować sposób, aby wtyczka rejestrowała się w określonych zdarzeniach, bez wcześniejszego ładowania. –

+0

Nie sądzę, że jest to możliwe, aby dll wtyczki zrobił * cokolwiek *, dopóki nie zostanie załadowany. Jednym z rozwiązań jest umieszczenie klasy definicji wtyczki w każdym zestawie wtyczek, który konfiguruje wtyczkę/mówi hostowi o jej możliwościach. Po uruchomieniu aplikacji można znaleźć wszystkie klasy definicji i podjąć odpowiednie działanie (może po prostu wywołanie metody "Konfiguruj" na każdym z nich). –

2

Do ładowania zestawów wtyczek mam tendencję do opierania się na moim kontenerze IoC w celu załadowania złożeń z katalogu (korzystam z StructureMap), chociaż można załadować je ręcznie zgodnie z odpowiedzią @ Oskara.

Jeśli chciałeś obsługiwać wtyczki ładujące podczas pracy aplikacji, StructureMap można "przekonfigurować", zbierając w ten sposób nowe wtyczki.

Dla haków aplikacji można wysyłać zdarzenia do magistrali zdarzeń. Poniższy przykład używa StructureMap znaleźć się wszystkie dotychczas zarejestrowane obsługi zdarzeń, ale można użyć zwykły stary odbicie lub innego kontenera IoC:

public interface IEvent { } 
public interface IHandle<TEvent> where TEvent : IEvent { 
    void Handle(TEvent e); 
} 

public static class EventBus { 
    public static void RaiseEvent(TEvent e) where TEvent : IEvent { 
     foreach (var handler in ObjectFactory.GetAllInstances<IHandle<TEvent>>()) 
      handler.Handle(e); 
    } 
} 

Następnie można podnieść zdarzenie tak:

public class Foo { 
    public Foo() { 
     EventBus.RaiseEvent(new FooCreatedEvent { Created = DateTime.UtcNow }); 
    } 
} 

public class FooCreatedEvent : IEvent { 
    public DateTime Created {get;set;} 
} 

i obsługiwać go (w swojej wtyczce na przykład) tak:

public class FooCreatedEventHandler : IHandle<FooCreatedEvent> { 
    public void Handle(FooCreatedEvent e) { 
     Logger.Log("Foo created on " + e.Created.ToString()); 
    } 
} 

zdecydowanie polecam this post przez Shannon Deminick która obejmuje wiele zagadnień z rozwijających pl nadające się aplikacje. To właśnie wykorzystaliśmy jako bazę dla naszego własnego "menedżera wtyczek".

Osobiście unikałbym ładowania zespołów na żądanie. IMO lepiej jest mieć nieco dłuższy czas uruchamiania (nawet mniejszy problem w aplikacji internetowej) niż użytkownicy aplikacji muszą czekać na załadowanie niezbędnych wtyczek.

+0

Interesujące. Wadą (przynajmniej dla mnie) jest narzut tworzenia nowej klasy dla każdego wydarzenia i za każdym razem chciałbym wystawić nowe wydarzenie w moim systemie. –

+0

Czas potrzebny na utworzenie tych lekkich zdarzeń i procedur obsługi jest prawdopodobnie mniej więcej taki sam, jak w przypadku tradycyjnego zdarzenia i obsługi. Nie widzę, że istnieje szybszy sposób publikowania nowych wydarzeń. –

+0

Również i (rodzaj) tracę możliwość definiowania zdarzeń ".NET" przez + = subskrybowanie ich. Wysyłanie zdarzeń odbywa się według twojego przykładu za pomocą wywołania metody + pozwalając kontenerowi rozwiązać "zasubskrybowane" klasy. –

5

Próbujesz rozwiązać nieistniejący problem. Obraz mentalny wszystkich typów ładowanych, gdy twój kod wywołuje Assembly.LoadFrom() jest błędny. Platforma .NET w pełni wykorzystuje zalety systemu Windows, który jest obsługiwanym przez żądanie systemem operacyjnym pamięci wirtualnej. A potem trochę.

Kula się toczy, gdy wywołasz LoadFrom(). Sprawia, że ​​CLR tworzy plik odwzorowany w pamięci , rdzenną abstrakcję systemu operacyjnego. Aktualizuje on nieco wewnętrzny stan, aby śledzić zespół będący rezydentem w AppDomain, jest bardzo niewielki. MMF ustawia odwzorowanie pamięci w górę, tworząc strony pamięci wirtualnej, które mapują zawartość pliku. Tylko mały deskryptor w TLB procesora. Nic nie jest faktycznie czytane z pliku zespołu.

Następnie użyj refleksji, aby odkryć typ implementujący interfejs. To powoduje, że CLR odczytuje niektóre z metadanych zespołu ze złożenia. W tym momencie błędy stron powodują, że procesor mapuje zawartość niektórych stron pokrywających sekcję metadanych zespołu do pamięci RAM. Garść kilobajtów, być może więcej, jeśli zestaw zawiera wiele typów.

Następnie kompilator just-in-time uruchamia się, aby wygenerować kod dla konstruktora. To powoduje, że procesor popełnia błąd na stronie zawierającej IL konstruktora w pamięci RAM.

Etcetera. Podstawową ideą jest to, że zawartość zestawu zawsze leniwie czytana jest tylko wtedy, gdy jest potrzebna .Mechanizm ten jest , a nie inny dla wtyczek, działają one tak samo, jak zwykłe zestawy w twoim rozwiązaniu. Jedyną różnicą jest to, że kolejność jest nieco inna. Najpierw ładujesz zespół, a następnie natychmiast wywołujesz konstruktora. W przeciwieństwie do wywoływania konstruktora typu w kodzie i CLR, a następnie natychmiastowego ładowania zespołu. Trwa to równie długo.

+0

Moje pytanie dotyczy przeniesienia odpowiedzialności za decyzję, kiedy wywołać wtyczkę z kodu klienta do samej wtyczki. W jaki sposób mogę pozwolić wtyczce powiedzieć, które zdarzenie powinno się załadować i wykonać, bez ładowania się zbyt szybko? Kinda sytuacji kurczaka i jaj. –

+1

Myślę, że Hans stara się powiedzieć, że twoje podejście RegisterForOperation1Hooks nie ładuje całego zestawu do pamięci, będąc dobrym rozwiązaniem twojego problemu :) – cwap

+0

Nie jestem pewien jak stworzyć typ zawarty w mojej wtyczce. dll i wywołanie metody Register() nie spowoduje wczytania biblioteki dll do pamięci. Moją motywacją jest oczywiście późniejszy załadowanie biblioteki DLL (zostanie załadowany do jej własnej AppDomain), nie będzie to możliwe przy użyciu tego rozwiązania. –

Powiązane problemy