代码之家  ›  专栏  ›  技术社区  ›  BuddyJoe

正确的加载程序集、查找类和调用run()方法的方法

  •  75
  • BuddyJoe  · 技术社区  · 15 年前

    控制台程序示例。

    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类强制转换为某个类?不确定一个程序集中的类型(动态代码)如何知道我的(静态程序集/shell应用程序)中的类型。仅仅使用几行反射代码在一个对象上调用run()更好吗?代码应该是什么样的?

    更新: 威廉·埃德蒙森-见评论

    5 回复  |  直到 7 年前
        1
  •  68
  •   Community Reversed Engineer    7 年前

    使用AppDomain

    将组件装入自己的组件更安全、更灵活。 AppDomain 第一。

    因此,而不是 the answer given previously :

    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();
    

    我建议如下(改编自 this answer to a related question ):

    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 命名空间)。有关更多信息,请参阅本文 Add-ins and Extensibility on MSDN .

        2
  •  46
  •   Jeff Sternal    15 年前

    如果您无法访问 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 接口类型,可以将实例强制转换为该类型(而不是 特斯特朗 类型,在动态创建或加载的程序集中实现,对吗?):

      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
  •  11
  •   Chris Doggett    15 年前

    我做的正是你在我的规则引擎中寻找的,它使用 CS-Script 用于动态编译、加载和运行C。它应该可以很容易地翻译成您正在寻找的内容,我将给出一个示例。首先,代码(删去部分):

    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's not, here's 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();
    }
    

    它可能对插件系统很有用,但我的代码仅限于加载和运行一个文件,因为我们的所有规则都在一个C源文件中。不过,我认为修改它非常容易,只需为您想要运行的每个文件传入类型/源文件即可。您只需要将代码从getter移到一个接受这两个参数的方法中。

    同时,用你的易怒来代替Itesturner。

        4
  •  5
  •   William Edmondson    15 年前

    您将需要使用反射来获取类型“testranner”。使用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
  •  2
  •   Sam Harwell    15 年前

    构建程序集时,可以调用 AssemblyBuilder.SetEntryPoint ,然后从 Assembly.EntryPoint 属性来调用它。

    请记住,您需要使用这个签名,并注意它不必命名。 Main :

    static void Run(string[] args)