Основная проблема здесь - думать, что вы можете создать плагин во время выполнения кода в основном домене приложения.
Вместо этого вам нужно создать прокси-тип, который определен в уже загруженной сборке, но создан в домене приложения new . Вы не можете передавать типы через границы домена приложения без загрузки сборки типа в оба домена приложения. Например, если вы хотите перечислять типы и печатать на консоль, как вы делали выше, вы должны делать это из кода, который выполняется в новом домене приложения, а не из кода, который выполняется в текущем домене приложения .
Итак, давайте создадим наш прокси-модуль плагина, он будет существовать в вашей основной сборке и будет отвечать за выполнение всего кода, связанного с плагином:
// Mark as MarshalByRefObject allows method calls to be proxied across app-domain boundaries
public class PluginRunner : MarshalByRefObject
{
// make sure that we're loading the assembly into the correct app domain.
public void LoadAssembly(byte[] byteArr)
{
Assembly.Load(byteArr);
}
// be careful here, only types from currently loaded assemblies can be passed as parameters / return value.
// also, all parameters / return values from this object must be marked [Serializable]
public string CreateAndExecutePluginResult(string assemblyQualifiedTypeName)
{
var domain = AppDomain.CurrentDomain;
// we use this overload of GetType which allows us to pass in a custom AssemblyResolve function
// this allows us to get a Type reference without searching the disk for an assembly.
var pluginType = Type.GetType(
assemblyQualifiedTypeName,
(name) => domain.GetAssemblies().Where(a => a.FullName == name.FullName).FirstOrDefault(),
null,
true);
dynamic plugin = Activator.CreateInstance(pluginType);
// do whatever you want here with the instantiated plugin
string result = plugin.RunTest();
// remember, you can only return types which are already loaded in the primary app domain and can be serialized.
return result;
}
}
Несколько ключевых моментов в комментариях выше я повторю здесь:
- Вы должны наследовать от
MarshalByRefObject
, это означает, что вызовы к этому объекту могут быть переданы через границы домена приложения с помощью удаленного взаимодействия.
- При передаче данных в прокси-класс или из него данные должны быть помечены
[Serializable]
, а также иметь тип, который находится в загруженной сборке. Если вам требуется, чтобы ваш плагин возвращал вам какой-то конкретный объект, скажем, PluginResultModel
, тогда вы должны определить этот класс в общей сборке, которая загружается обеими сборками / доменами приложений.
- Необходимо передать квалифицированное имя типа сборки в
CreateAndExecutePluginResult
в его текущем состоянии, но можно было бы удалить это требование, выполнив итерации сборок и типов самостоятельно и удалив вызов Type.GetType
.
Далее необходимо создать домен и запустить прокси:
static void Main(string[] args)
{
var bytes = File.ReadAllBytes(@"...filepath...");
var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);
var proxy = (PluginRunner)domain.CreateInstanceAndUnwrap(typeof(PluginRunner).Assembly.FullName, typeof(PluginRunner).FullName);
proxy.LoadAssembly(bytes);
proxy.CreateAndExecutePluginResult("TestPlugin.Class1, TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
}
Собираюсь сказать это снова, потому что это очень важно, и я долго не понимал этого: когда вы выполняете метод для этого прокси-класса, такой как proxy.LoadAssembly
, это фактически сериализуется в строку и передается в новый домен приложения для выполнения. Это не обычный вызов функции, и вам нужно быть очень осторожным с тем, что вы передаете в / из этих методов.