2012-04-24 29 views
8

Tôi đang xây dựng một ứng dụng dựa trên .NET và muốn cho phép một thiết kế mở rộng và có thể cắm thêm được.Cho phép các trình cắm thêm C# đăng ký trên các móc ứng dụng

Vì lợi ích của sự đơn giản, ứng dụng cho thấy một tập hợp các hoạt động và các sự kiện:

  • DoSomething()
  • DoOtherThing()
  • onerror
  • onSuccess

tôi muốn cung cấp "plugins" để được tải và móc vào một số các hoạt động này (giống như: Khi Event1 kích hoạt, hãy chạy plugin1).

Ví dụ - chạy plugin1.HandleError() khi sự kiện OnError kích hoạt.

Điều này có thể được thực hiện dễ dàng với thuê bao sự kiện:

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

Vấn đề là:

  1. Ứng dụng của tôi không biết loại "Plugin1" (nó là một plugin, tôi ứng dụng không tham chiếu trực tiếp).
  2. Làm như vậy sẽ khởi tạo plugin trước thời gian, điều tôi không làm muốn thực hiện.

Trong các mô hình plugin "truyền thống", ứng dụng ("máy khách" của plugin) tải và thực thi mã plugin tại một số điểm chính. Ví dụ: ứng dụng xử lý hình ảnh, khi thực hiện một số thao tác nhất định).

Kiểm soát thời điểm khởi tạo mã plugin và thời điểm thực thi nó được biết đến với ứng dụng khách.

Trong ứng dụng của tôi, chính plugin đó là để quyết định khi nào cần thực thi ("Plugin nên đăng ký trên sự kiện OnError").

Việc giữ mã "thực thi" cùng với mã "đăng ký" đặt ra một vấn đề mà plugin DLL với sẽ được tải vào bộ nhớ vào thời điểm đăng ký, điều mà tôi muốn ngăn chặn.

Ví dụ: nếu tôi thêm phương thức Register() vào plugin DLL, plugin DLL sẽ phải được tải vào bộ nhớ để phương thức đăng ký được gọi.

Giải pháp thiết kế tốt cho vấn đề cụ thể này là gì?

  • Tải nhẹ nhàng (hoặc cung cấp tải chậm/háo hức) của plugin DLL.
  • Cho phép plugin kiểm soát các phần khác nhau của hệ thống/ứng dụng mà chúng móc vào.

Trả lời

4

Những gì bạn cần làm là tìm đường dẫn của dll và sau đó tạo đối tượng lắp ráp từ nó.Từ đó, bạn sẽ cần phải nhận được lớp bạn muốn lấy (ví dụ, bất cứ điều gì mà thực hiện giao diện của bạn):

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 
} 

Tất nhiên, từ đây bạn có thể tự do làm bất cứ điều gì bạn muốn, phương pháp sử dụng, đăng ký Các sự kiện, vv

+1

nhược điểm của chiến lược này là rất chậm khi tải lắp ráp. Đây là vấn đề phổ biến cho sự phản ánh trong .Net –

+0

Cảm ơn tôi đã biết làm thế nào để làm điều này. Những gì tôi muốn thiết kế là một cách để cho phép các plugin để đăng ký vào các sự kiện nhất định, mà không cần tải nó trước. –

+0

Tôi không nghĩ rằng nó có thể cho dll plugin để làm * bất cứ điều gì * cho đến khi nó thực sự được nạp. Một giải pháp là đặt một lớp định nghĩa của plugin trong mỗi assembly lắp đặt plugin/nói với máy chủ về khả năng của nó. Khi ứng dụng của bạn bắt đầu, bạn có thể tìm thấy tất cả các lớp định nghĩa và thực hiện hành động thích hợp (có lẽ chỉ cần gọi phương thức "Cấu hình" trên mỗi). –

2

Để tải các hội đồng plugin Tôi có xu hướng dựa vào container IoC để tải các assembly từ một thư mục (tôi sử dụng StructureMap), mặc dù bạn có thể tải chúng theo cách thủ công của @ Oskar.

Nếu bạn muốn hỗ trợ tải plugin trong khi ứng dụng của bạn đang chạy, StructureMap có thể được "cấu hình lại", do đó chọn bất kỳ plugin mới nào.

Đối với ứng dụng của bạn, bạn có thể gửi các sự kiện đến một xe buýt sự kiện. Ví dụ dưới đây sử dụng StructureMap để tìm tất cả xử lý sự kiện đăng ký, nhưng bạn có thể sử dụng phản chiếu cũ đồng bằng này hay cách khác container 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); 
    } 
} 

Sau đó bạn có thể nâng cao một sự kiện như vậy:

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

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

Và xử lý nó (trong plugin của bạn chẳng hạn) như sau:

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

tôi chắc chắn sẽ khuyên this post bởi Shannon Deminick trong đó bao gồm nhiều vấn đề với việc phát triển pl ứng dụng khả thi. Đó là những gì chúng tôi sử dụng làm cơ sở cho "trình quản lý plugin" của chính chúng tôi.

Cá nhân tôi sẽ tránh tải các hội đồng theo yêu cầu. IMO tốt hơn là nên có thời gian khởi động hơi lâu hơn (thậm chí ít hơn một vấn đề trên ứng dụng web) so với người dùng của ứng dụng đang chạy phải chờ các plugin cần thiết được tải.

+0

Thú vị. Hạn chế (đối với tôi ít nhất) là chi phí của việc tạo ra một lớp mới cho mỗi sự kiện, và mỗi khi tôi muốn để lộ một sự kiện mới trong hệ thống của tôi. –

+0

Thời gian cần thiết để tạo ra các sự kiện nhẹ và trình xử lý này có thể giống như việc thực hiện một sự kiện truyền thống và trình xử lý. Tôi không thể thấy rằng có một cách nhanh hơn để xuất bản các sự kiện mới. –

+0

Ngoài ra, tôi (loại) mất khả năng xác định các sự kiện .NET 'bằng cách + = đăng ký với chúng. Việc gửi đi sự kiện được thực hiện theo ví dụ của bạn bằng cách sử dụng một cuộc gọi phương thức + cho phép vùng chứa giải quyết các lớp 'đã đăng ký'. –

5

Bạn đang cố giải quyết sự cố không tồn tại. Hình ảnh tinh thần của tất cả các loại được tải khi mã của bạn gọi Assembly.LoadFrom() là sai. Khuôn khổ .NET tận dụng tối đa Windows là một hệ điều hành bộ nhớ ảo theo yêu cầu. Và sau đó, vài.

Bóng sẽ lăn khi bạn gọi LoadFrom(). Việc làm cho CLR tạo ra một tệp ánh xạ bộ nhớ , một hệ điều hành trừu tượng của lõi. Nó cập nhật một chút trạng thái bên trong để theo dõi một assembly đang cư trú trong AppDomain, nó rất nhỏ. MMF thiết lập ánh xạ bộ nhớ lên, tạo các trang bộ nhớ ảo ánh xạ nội dung của tệp. Chỉ là một bộ mô tả nhỏ trong TLB của bộ vi xử lý. Không có gì thực sự được đọc từ tập tin lắp ráp.

Tiếp theo, bạn sẽ sử dụng phản chiếu để tìm cách khám phá một loại thực hiện giao diện. Điều đó làm cho CLR đọc một số siêu dữ liệu lắp ráp từ assembly. Tại thời điểm này, lỗi trang khiến bộ xử lý ánh xạ nội dung của một số trang bao gồm phần siêu dữ liệu của hội đồng vào RAM. Một số kilobyte, có thể nhiều hơn nếu lắp ráp chứa rất nhiều loại.

Tiếp theo, trình biên dịch vừa mới bắt đầu hoạt động để tạo mã cho hàm tạo. Điều đó khiến bộ vi xử lý gây lỗi cho trang chứa hàm tạo IL vào RAM.

Etcetera. Ý tưởng cốt lõi là nội dung lắp ráp luôn bị đọc một cách lười biếng, chỉ khi cần.Cơ chế này là không khác nhau cho plugin, chúng hoạt động giống như các cụm thông thường trong giải pháp của bạn. Với sự khác biệt duy nhất là thứ tự hơi khác một chút. Bạn tải lắp ráp đầu tiên, sau đó ngay lập tức gọi các nhà xây dựng. Trái ngược với việc gọi hàm tạo của một kiểu trong mã của bạn và CLR thì ngay lập tức tải assembly. Nó chỉ mất nhiều thời gian.

+0

Câu hỏi của tôi là về việc chuyển trách nhiệm quyết định Khi nào nên gọi trình cắm từ mã máy khách đến chính plugin đó. Làm thế nào để tôi cho phép plugin nói sự kiện nào sẽ tự tải và thực thi, mà không tải bản thân quá sớm? Kinda gà và trứng tình hình. –

+1

Tôi nghĩ rằng những gì Hans đang cố gắng nói là, cách tiếp cận RegisterForOperation1Hooks của bạn không tải lắp ráp đầy đủ vào bộ nhớ, do đó là một giải pháp tốt cho vấn đề của bạn :) – cwap

+0

Tôi không chắc chắn cách tạo một loại chứa trong Plugin của tôi. dll và gọi phương thức Register() trên nó sẽ không khiến dll nạp vào bộ nhớ. Động cơ của tôi là tất nhiên dỡ bỏ các DLL sau này (sẽ được nạp vào AppDomain riêng của nó), điều này sẽ không thể sử dụng giải pháp này. –

Các vấn đề liên quan