Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Общие вопросы по .NET и C# > Общая схема реализации плагинов


Автор: Medved 22.12.2004, 14:20
Подскажите общую схему реализации плагинов в своей программе. Какие могутт быть подводные камни?. Планируется это осуществлять для крупного и довольно сложного web-сервиса.

Автор: Mad 22.12.2004, 14:25
Мы используем такую схему:

AppDomain.Current.LoadAssembly() для загрузки плагина
потом через Runtime или создаеш обьект класса, или вызываеш static функции

Но есть один минус : выгрузить dll уже нельзя smile

Автор: Medved 22.12.2004, 15:09
А можно поподробнее?
Добавлено @ 15:09
Я планировал реализовывать через интерфейсы.

Автор: Mad 22.12.2004, 15:20
Вот класс для загрузки плагинов
Код

using System;
using System.IO;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Windows.Forms;
using System.Xml;
using System.Collections;

namespace omis
{
/// <summary>
/// Summary description for Loader.
/// </summary>
public class Loader
{
 public Loader()
 {
 }

 public static object LoadModule(string name, object arg)
 {
  Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
  for (int i=0; i<asms.Length; i++)
  {
   Type typ = asms[i].GetType(name, false, true);
   if (typ != null)
   {
    try
    {
     object[] args = new object[1];
     args[0] = arg;
     return typ.InvokeMember(null, BindingFlags.CreateInstance, null, null, args);
    }
    catch (Exception ex)
    {
     omis.console.Console.ReportWarning("omis.Loader", "Failed create instance of "+name, ex.Message+"\r\n"+ex.StackTrace);
    }
   }
  }
  return null;
 }

 public static Type[] GetTypeFromBase(string name)
 {
  ArrayList cach = new ArrayList();
  Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
  for (int i=0; i<asms.Length; i++)
  {
   Type[] tp = asms[i].GetTypes();
   for (int k=0; k<tp.Length; k++)
   {
    if (tp[k].BaseType != null && tp[k].BaseType.ToString().ToLower() == name)
     cach.Add(tp[k]);
   }
  }

  if (cach.Count > 0)
  {
   Type[] ret = new Type[cach.Count];
   for (int i=0; i<cach.Count; i++)
    ret[i] = (Type)cach[i];
   cach.Clear();
   return ret;
  }
  return null;
 }

 public static Type GetModule(string name)
 {
  Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
  for (int i=0; i<asms.Length; i++)
  {
   Type typ = asms[i].GetType(name, false, true);
   if (typ != null)
   {
    return typ;
   }
  }

  string[] sname = name.Split('.');
  string pname = "../libs";
  for (int i=0; i<sname.Length-1; i++)
   pname += "/"+sname[i];
  string fname = pname+"/"+sname[sname.Length-1];

  try
  {
   StreamReader fs = new StreamReader(fname+".cs", System.Text.Encoding.GetEncoding("WINDOWS-1251"));
   string src = fs.ReadToEnd();
   fs.Close();

   CSharpCodeProvider code = new CSharpCodeProvider();
   ICodeCompiler comp = code.CreateCompiler();

   CompilerParameters opt = new CompilerParameters();
   opt.GenerateInMemory = true;
   opt.GenerateExecutable = false;
   opt.OutputAssembly = null; //fname+".dll";
   opt.IncludeDebugInformation = false;
   opt.ReferencedAssemblies.Add("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.3705\\System.Xml.dll");
   opt.ReferencedAssemblies.Add("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.3705\\System.Windows.Forms.dll");
   opt.ReferencedAssemblies.Add("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.3705\\System.Drawing.dll");
   opt.ReferencedAssemblies.Add("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.3705\\System.dll");
   CompilerResults res = comp.CompileAssemblyFromSource(opt, src);

   if (res.Errors.Count > 0)
   {
    for (int i=0; i < res.Errors.Count; i++)
     omis.console.Console.ReportWarning("omis.Loader.GetModule", res.Errors[i].ErrorText, fname);
   }
   else
   {
    Type typ = res.CompiledAssembly.GetType(name, true, true);
    if (typ != null)
    {
     return typ;
    }
   }
  }
  catch (Exception ex)
  {
   omis.console.Console.ReportError("omis.Loader.GetModule", ex.Message, fname);
  }
  return null;
 }
}
}


LoadModule() возвращает обьект класса по имени

Автор: Medved 22.12.2004, 15:34
Посмотрел вскольз я на пример, и ИМХО через интерфесы будет более универсально, и к тому же более приближено к пардиаграме ООП. Хотя возможно что я и ошибаюсь. Но в любом случае спасибо!

Автор: Medved 23.12.2004, 14:16
НИкто никогда не реализовывал механизм плагинов?

Автор: Mad 23.12.2004, 14:22
Pegas
Кстати , сам об этом не думал smile, мой пример применим и к интерфейсам (надо только заменить возвращаемый тип с object на нужный тебе интерфейс). Мы просто приводим возвращаемый обьект к нужному классу (если обьект другого класса .NET генерить Exception).

Автор: arilou 23.12.2004, 15:46
Мои пять копеек smile Для реализации плагинов используется следующий стандартный подход:

1) С помощью интерфейсов определяется стандартная схема взаимодействия плагина с хост-приложением и наоборот. Например, интерфейс дает возможность плагину сообщить, в каком меню, после какого и перед каким элементом он должен появиться

2) Основное приложение при запуске или считывает информацию о том, какие плагины надо загрузить, например, из конфига, или берет все DLL'ки из определенной папки.

3) С помощью Reflection'a из сборок, содержащих плагины, достается информация о том, какой именно класс реализует искомый интерфейс IPlugin

4) С помощью того же Reflection'a создается экземпляр основного класса-плагина. Он приводится к интерфейсу IPlugin, и через него происходит собственно взаимодействие

Хороший пример реализации плагинов используется в среде разработки http://www.sharpdevelop.com. На сайте можно скачать source code и доки, её описывающие.
Добавлено @ 15:48
Цитата(Mad @ 22.12.2004, 14:25)
Но есть один минус : выгрузить dll уже нельзя 

В таких случах плагин грузят в другой домен. Домены выгружать можно, и, кстати, это защищает основное приложение от крэшев в плагинах.

Автор: Mad 23.12.2004, 15:57
Цитата(arilou @ 23.12.2004, 14:46)
В таких случах плагин грузят в другой домен.

Появляються сложности общения с основным приложением (обмен данными)

Цитата(arilou @ 23.12.2004, 14:46)
это защищает основное приложение от крэшев в плагинах.

Для этого просто ставиться свой обработчик Exception (Application.ThreadException)

Автор: arilou 23.12.2004, 16:38
Цитата(Mad @ 23.12.2004, 15:57)
Появляються сложности общения с основным приложением (обмен данными)

В случае, если плагины может разрабатывать треья сторона, их надо изолировать. Я считаю, что потратив сразу некоторое время на создание стандартного мех-ма взаимодействия плагина с основной программой (обертку вокруг CrossDomainDelegate), можно отсечь возможные проблемы в будущем.

Цитата(Mad @ 23.12.2004, 15:57)
Для этого просто ставиться свой обработчик Exception (Application.ThreadException)

Есть exception'ы, которые не ловятся Application.ThreadException. Видел не один раз, особенно в релиз-версии.

Автор: Mad 23.12.2004, 16:45
Цитата(arilou @ 23.12.2004, 15:38)
Есть exception'ы, которые не ловятся Application.ThreadException. Видел не один раз, особенно в релиз-версии.

Например ?

Автор: arilou 23.12.2004, 16:57
Приложение работает некоторое время (час, 5 минут, пол-дня). Потом вылазит сообщение "Unhandled exception, thread id bla bla, process id bla bla". И всё. Во всех возможных местах стоят обработчики. Детально описать не получится - приложение сложное.
Добавлено @ 17:00
Цитата(Mad @ 22.12.2004, 15:20)
Код

for (int i=0; i<asms.Length; i++)
 {
  Type typ = asms[i].GetType(name, false, true);
  if (typ != null)
  {
   return typ;
  }
 }


Вопрос не по теме - а почему foreach не используешь?

Автор: Mad 23.12.2004, 17:02
Цитата(arilou @ 23.12.2004, 15:57)
Потом вылазит сообщение "Unhandled exception, thread id bla bla, process id bla bla".

Это глюк 1.0 (вылетает сама VM) и уже не важно в каком AppDomain это произошло, закрывается все приложение (а иногда и все .NET приложения на тачке smile) в 1.1 вроде такого уже не наблюдается.
Добавлено @ 17:05
Цитата(arilou @ 23.12.2004, 15:57)
а почему foreach не используешь?

Я отот класс писал когда тока начал C# учить (больше технология интересовалла чем особенности языка).
Да и теперь восновном for использую, привычней smile

Автор: Medved 28.12.2004, 00:26
Цитата(Mad @ 23.12.2004, 20:02)
Я отот класс писал когда тока начал C# учить (больше технология интересовалла чем особенности языка).
Да и теперь восновном for использую, привычней smile


Я наоборот стараюсь реализовывать интерфейсы IComparable и IComparer в своих пользовательских классах, чтобы можно было применять к массивам этих объектова цикл forech. Чертовски удобно.

Автор: arilou 28.12.2004, 13:43
Цитата(Pegas @ 28.12.2004, 00:26)
Я наоборот стараюсь реализовывать интерфейсы IComparable и IComparer в своих пользовательских классах, чтобы можно было применять к массивам этих объектова цикл forech. Чертовски удобно.

А что, ты не наследушь классы коллекций от стандартных (например, ArrayList) ?

Автор: Medved 29.12.2004, 03:16
Были определенные заморочки, в результате чего я отказался, от наследования о котором ты говоришь. Хотя в стандартной ситуации согласен, это оптимальный вариант.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)