Как загрузить плагины в .NET?
Я хотел бы предоставить способ создания динамически загружаемых плагинов в моем программном обеспечении. Типичный способ сделать это - использовать функцию LoadLibrary WinAPI для загрузки dll и вызвать GetProcAddress для получения указателя на функцию внутри этой dll.
Мой вопрос: как мне динамически загрузить плагин в приложение C# /. Net?
Ответов (8)8
Следующий фрагмент кода (C#) создает экземпляр любых конкретных классов, производных от Base
библиотек классов (* .dll) в пути к приложению, и сохраняет их в списке.
using System.IO;
using System.Reflection;
List<Base> objects = new List<Base>();
DirectoryInfo dir = new DirectoryInfo(Application.StartupPath);
foreach (FileInfo file in dir.GetFiles("*.dll"))
{
Assembly assembly = Assembly.LoadFrom(file.FullName);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(Base)) && type.IsAbstract == false)
{
Base b = type.InvokeMember(null,
BindingFlags.CreateInstance,
null, null, null) as Base;
objects.Add(b);
}
}
}
Изменить: классы, на которые ссылается Мэтт , вероятно, являются лучшим вариантом в .NET 3.5.
Это моя реализация, вдохновленная этим кодом, избегая перебора всех сборок и всех типов (или, по крайней мере, фильтрации с помощью linQ). Я просто загружаю библиотеку и пытаюсь загрузить класс, реализующий общий общий интерфейс. Просто и быстро :)
Просто объявите интерфейс в отдельной библиотеке и сделайте ссылку на него как в вашей системе, так и в вашем плагине:
public interface IYourInterface
{
Task YourMethod();
}
В своей библиотеке плагинов объявите класс, реализующий IYourInterface.
public class YourClass: IYourInterface
{
async Task IYourInterface.YourMethod()
{
//.....
}
}
В своей системе объявите этот метод
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;
public abstract class ReflectionTool<TSource> where TSource : class
{
public static TSource LoadInstanceFromLibrary(string libraryPath)
{
TSource pluginclass = null;
if (!System.IO.File.Exists(libraryPath))
throw new Exception($"Library '{libraryPath}' not found");
else
{
Assembly.LoadFrom(libraryPath);
var fileName = System.IO.Path.GetFileName(libraryPath).Replace(".dll", "");
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(c => c.FullName.StartsWith(fileName));
var type = assembly.GetTypes().FirstOrDefault(c => c.GetInterface(typeof(TSource).FullName) != null);
try
{
pluginclass = Activator.CreateInstance(type) as TSource;
}
catch (Exception ex)
{
LogError("", ex);
throw;
}
}
return pluginclass;
}
}
И назовите это так:
IYourInterface instance = ReflectionTool<IYourInterface>.LoadInstanceFromLibrary("c:\pathToYourLibrary.dll");
Динамическая загрузка подключаемых модулей
Для получения информации о том, как динамически загружать сборки .NET, см. Этот вопрос (и мой ответ ). Вот код для загрузки создания AppDomain
и загрузки в него сборки.
var domain = AppDomain.CreateDomain("NewDomainName");
var pathToDll = @"C:\myDll.dll";
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(pathToDll, t.FullName)
as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
Выгрузка плагинов
Типичное требование фреймворка плагинов - выгрузить плагины. Чтобы выгрузить динамически загружаемые сборки (например, плагины и надстройки), вы должны выгрузить содержимое AppDomain
. Дополнительные сведения см. В этой статье MSDN о выгрузке доменов приложений .
Использование WCF
Существует вопрос и ответ о переполнении стека , в которых описывается, как использовать Windows Communication Framework (WCF) для создания инфраструктуры подключаемых модулей.
Существующие платформы для подключаемых модулей
Я знаю два фреймворка для плагинов:
- Mono.Add-ins - как упоминалось в этом ответе на другой вопрос .
- Платформа управляемых надстроек (MAF) - это
System.AddIn
пространство имен, упомянутое Мэттом в своем ответе .
Некоторые люди говорят о Managed Extensibility Framework (MEF) как о надстройке или надстройке, но это не так. Для получения дополнительной информации см. Этот вопрос StackOverflow.com и этот вопрос StackOverflow.com .
В основном это можно сделать двумя способами.
Первый - импортировать kernel32.dll и использовать LoadLibrary и GetProcAddress, как вы использовали их раньше:
[DllImport("kernel32.dll")]
internal static extern IntPtr LoadLibrary(String dllname);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetProcAddress(IntPtr hModule, String procname);
Второй - сделать это .NET-способом: с помощью отражения. Проверьте пространство имен System.Reflection и следующие методы:
Сначала вы загружаете сборку по ее пути, затем получаете от нее тип (класс) по ее имени, затем снова получаете метод класса по его имени и, наконец, вызываете метод с соответствующими параметрами.
Один совет - загрузить все плагины и тому подобное в собственный домен приложений, поскольку выполняемый код может быть потенциально вредоносным. Собственный домен приложения также можно использовать для «фильтрации» сборок и типов, которые вы не хотите загружать.
AppDomain domain = AppDomain.CreateDomain("tempDomain");
И чтобы загрузить сборку в домен приложения:
AssemblyName assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
Assembly assembly = domain.Load(assemblyName);
Чтобы выгрузить домен приложения:
AppDomain.Unload(domain);
Начиная с .NET 3.5, существует формализованный встроенный способ создания и загрузки подключаемых модулей из приложения .NET. Все это в пространстве имен System.AddIn . Для получения дополнительной информации вы можете ознакомиться с этой статьей на MSDN: Надстройки и расширяемость.
Да, ++ для Мэтта и System.AddIn (двухчастная статья журнала MSDN о System.AddIn доступна здесь и здесь ). Еще одна технология, на которую вы, возможно, захотите взглянуть, чтобы получить представление о возможном развитии .NET Framework в будущем, - это Managed Extensibility Framework, доступная в настоящее время в форме CTP на Codeplex.