Подтвердить что ты не робот

Правильный способ загрузки сборки, поиска метода вызова и вызова()

Пример программы консоли.

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: Уильям Эдмондсон - см. Комментарий

4b9b3361

Ответ 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)