Правильный способ загрузки сборки, поиска метода вызова и вызова()
Пример программы консоли.
class Program
{
static void Main(string[] args)
{
// ... code to build dll ... not written yet ...
Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
// don't know what or how to cast here
// looking for a better way to do next 3 lines
IRunnable r = assembly.CreateInstance("TestRunner");
if (r == null) throw new Exception("broke");
r.Run();
}
}
Я хочу динамически строить сборку (DLL), а затем загружать сборку, создавать экземпляр класса и вызывать метод Run() этого класса. Должен ли я попробовать что-то отличить класс TestRunner? Не знаете, как типы в одной сборке (динамическом коде) будут знать о моих типах в моем (статическое приложение сборки/оболочки). Лучше просто использовать несколько строк кода отражения, чтобы вызвать Run() только на одном объекте? Как выглядит этот код?
UPDATE:
Уильям Эдмондсон - см. Комментарий
Ответы
Ответ 1
Использовать AppDomain
Более безопасно и гибко загружать сборку в свой собственный AppDomain
.
Итак, вместо ответа, полученного ранее:
var asm = Assembly.LoadFile(@"C:\myDll.dll");
var type = asm.GetType("TestRunner");
var runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
Я бы предложил следующее (адаптировано из этого ответа на соответствующий вопрос):
var domain = AppDomain.CreateDomain("NewDomainName");
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(@"C:\myDll.dll", t.Name) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
Теперь вы можете выгрузить сборку и выполнить различные настройки безопасности.
Если вы хотите еще большую гибкость и мощность для динамической загрузки и выгрузки сборок, вы должны посмотреть на управляемую инфраструктуру надстроек (то есть пространство имен System.AddIn
). Для получения дополнительной информации см. Эту статью в надстройки и расширяемость в MSDN.
Ответ 2
Если у вас нет доступа к информации типа TestRunner
в вызывающей сборке (похоже, это не так), вы можете вызвать метод следующим образом:
Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
Type type = assembly.GetType("TestRunner");
var obj = Activator.CreateInstance(type);
// Alternately you could get the MethodInfo for the TestRunner.Run method
type.InvokeMember("Run",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
null);
Если у вас есть доступ к типу интерфейса IRunnable
, вы можете указать свой экземпляр для этого (а не типа TestRunner
, который реализован в динамически созданной или загруженной сборке, правильно?):
Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
Type type = assembly.GetType("TestRunner");
IRunnable runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
Ответ 3
Я делаю именно то, что вы ищете в моем движке правил, который использует CS- Script для динамической компиляции, загрузки, и запуск С#. Он должен быть легко переводим в то, что вы ищете, и я приведу пример. Во-первых, код (урезанный):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using CSScriptLibrary;
namespace RulesEngine
{
/// <summary>
/// Make sure <typeparamref name="T"/> is an interface, not just any type of class.
///
/// Should be enforced by the compiler, but just in case it not, here your warning.
/// </summary>
/// <typeparam name="T"></typeparam>
public class RulesEngine<T> where T : class
{
public RulesEngine(string rulesScriptFileName, string classToInstantiate)
: this()
{
if (rulesScriptFileName == null) throw new ArgumentNullException("rulesScriptFileName");
if (classToInstantiate == null) throw new ArgumentNullException("classToInstantiate");
if (!File.Exists(rulesScriptFileName))
{
throw new FileNotFoundException("Unable to find rules script", rulesScriptFileName);
}
RulesScriptFileName = rulesScriptFileName;
ClassToInstantiate = classToInstantiate;
LoadRules();
}
public T @Interface;
public string RulesScriptFileName { get; private set; }
public string ClassToInstantiate { get; private set; }
public DateTime RulesLastModified { get; private set; }
private RulesEngine()
{
@Interface = null;
}
private void LoadRules()
{
if (!File.Exists(RulesScriptFileName))
{
throw new FileNotFoundException("Unable to find rules script", RulesScriptFileName);
}
FileInfo file = new FileInfo(RulesScriptFileName);
DateTime lastModified = file.LastWriteTime;
if (lastModified == RulesLastModified)
{
// No need to load the same rules twice.
return;
}
string rulesScript = File.ReadAllText(RulesScriptFileName);
Assembly compiledAssembly = CSScript.LoadCode(rulesScript, null, true);
@Interface = compiledAssembly.CreateInstance(ClassToInstantiate).AlignToInterface<T>();
RulesLastModified = lastModified;
}
}
}
Это займет интерфейс типа T, скомпилирует файл .cs в сборку, создаст экземпляр класса заданного типа и выровняем этот экземпляр класса с интерфейсом T. В принципе, вам просто нужно убедиться, что экземпляр класса реализует этот интерфейс. Я использую свойства для настройки и доступа ко всем, например:
private RulesEngine<IRulesEngine> rulesEngine;
public RulesEngine<IRulesEngine> RulesEngine
{
get
{
if (null == rulesEngine)
{
string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs");
rulesEngine = new RulesEngine<IRulesEngine>(rulesPath, typeof(Rules).FullName);
}
return rulesEngine;
}
}
public IRulesEngine RulesEngineInterface
{
get { return RulesEngine.Interface; }
}
В вашем примере вы хотите вызвать Run(), поэтому я бы создал интерфейс, который определяет метод Run(), например:
public interface ITestRunner
{
void Run();
}
Затем создайте класс, который его реализует, например:
public class TestRunner : ITestRunner
{
public void Run()
{
// implementation goes here
}
}
Измените имя RulesEngine на нечто вроде TestHarness и задайте свои свойства:
private TestHarness<ITestRunner> testHarness;
public TestHarness<ITestRunner> TestHarness
{
get
{
if (null == testHarness)
{
string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs");
testHarness = new TestHarness<ITestRunner>(sourcePath , typeof(TestRunner).FullName);
}
return testHarness;
}
}
public ITestRunner TestHarnessInterface
{
get { return TestHarness.Interface; }
}
Затем, в любом месте, которое вы хотите назвать, вы можете просто запустить:
ITestRunner testRunner = TestHarnessInterface;
if (null != testRunner)
{
testRunner.Run();
}
Это, вероятно, отлично подойдет для плагиновой системы, но мой код as-is ограничен загрузкой и запуском одного файла, так как все наши правила находятся в одном исходном файле С#. Я бы подумал, что было бы довольно легко изменить его, чтобы просто передать файл типа/источника для каждого из них, который вы хотели запустить. Вам просто нужно переместить код из получателя в метод, который принял эти два параметра.
Кроме того, используйте свой IRunnable вместо ITestRunner.
Ответ 4
Вам нужно будет использовать отражение, чтобы получить тип "TestRunner". Используйте метод Assembly.GetType.
class Program
{
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
Type type = assembly.GetType("TestRunner");
var obj = (TestRunner)Activator.CreateInstance(type);
obj.Run();
}
}
Ответ 5
Когда вы создаете свою сборку, вы можете вызвать AssemblyBuilder.SetEntryPoint
, а затем вернуть ее из Assembly.EntryPoint
, чтобы вызвать его.
Имейте в виду, что вы захотите использовать эту подпись и обратите внимание, что ее не нужно называть Main
:
static void Run(string[] args)