在C#中运行时加载DLLs

107

我正在尝试找出如何在C#应用程序内部运行时导入和使用.dll文件。使用Assembly.LoadFile(),我已经成功加载了dll文件(这部分肯定有效,因为我能够用ToString()获取类名),但是我无法在控制台应用程序内部使用"Output"方法。我将.dll编译后移动到我的控制台项目中。在CreateInstance和能够使用方法之间是否有额外的步骤?

这是我DLL中的类:

namespace DLL
{
    using System;

    public class Class1
    {
        public void Output(string s)
        {
            Console.WriteLine(s);
        }
    }
}

这里是我想要加载 DLL 的应用程序

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}

https://dev59.com/lHE95IYBdhLWcg3wrP-w - Stu
7个回答

148

为了能够直接从C#中调用成员,这些成员必须在编译时可解析。否则,您必须使用反射或动态对象。

反射

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                type.InvokeMember("Output", BindingFlags.InvokeMethod, null, c, new object[] {@"Hello"});
            }

            Console.ReadLine();
        }
    }
}

动态 (.NET 4.0)

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                dynamic c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}

13
请注意,这会在程序集中对每个类型尝试调用 Output 方法,这很可能会在找到“正确”的类之前抛出异常... - Reed Copsey
1
@ReedCopsey,同意您的观点,但就他所举的简单例子而言,他定义的类型是唯一可见的。"只有公共类型及嵌套于其他公共类型中的类型才会对外部程序集可见。"显然,在一个复杂的示例中,这将是一个问题... - Dark Falcon
1
两个例子非常优秀! :) - Niels Abildgaard
28
因此,接口经常被使用,您可以进行特性检测,例如 IDog dog = someInstance as IDog; 并测试其是否为非null。将您的接口放入由客户端共享的公共DLL中,任何动态加载的插件都必须实现该接口。这样,您就可以针对IDog接口编写客户端代码,并在编译时具有智能感知和强类型检查,而不是使用动态方式。 - AaronLS
2
@Tarek.Mh:这将需要在编译时依赖于Class1。此时,您可以直接使用new Class1()。提问者明确指定了运行时依赖项。dynamic允许程序根本不需要在编译时对Class1进行依赖。 - Dark Falcon
显示剩余2条评论

48

现在,您正在创建程序集中定义的每种类型的一个实例。您只需要创建一个Class1 的实例来调用该方法:

class Program
{
    static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var theType = DLL.GetType("DLL.Class1");
        var c = Activator.CreateInstance(theType);
        var method = theType.GetMethod("Output");
        method.Invoke(c, new object[]{@"Hello"});

        Console.ReadLine();
    }
}

24
您需要创建一个实例,该实例公开Output方法的类型:
static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var class1Type = DLL.GetType("DLL.Class1");

        //Now you can use reflection or dynamic to call the method. I will show you the dynamic way

        dynamic c = Activator.CreateInstance(class1Type);
        c.Output(@"Hello");

        Console.ReadLine();
     }

非常感谢,这正是我所寻找的。我无法相信这个答案没有得到比其他答案更高的评价,因为它展示了动态关键字的使用。 - skiphoppy
啊,现在我看到DarkFalcon的回答里也有了。不过你的更短,更容易看到 :) - skiphoppy

0

Activator.CreateInstance() 返回一个对象,该对象没有 Output 方法。

看起来你来自动态编程语言?C# 明显不是这样的,而且你想做的事情会很困难。

既然你正在从特定位置加载特定的 dll,也许你只想将其添加为控制台应用程序的引用?

如果你绝对想通过 Assembly.Load 加载程序集,那么你将不得不通过反射来调用 c 上的任何成员。

类似于 type.GetMethod("Output").Invoke(c, null); 应该可以解决问题。


0
foreach (var f in Directory.GetFiles(".", "*.dll"))
            Assembly.LoadFrom(f);

这会加载在您可执行文件的文件夹中存在的所有DLL。

在我的情况下,我尝试使用反射来查找类的所有子类,即使在其他DLL中也是如此。 这个方法有效,但我不确定这是否是最好的方法。

编辑:我计时了一下,它似乎只在第一次加载它们。

Stopwatch stopwatch = new Stopwatch();
for (int i = 0; i < 4; i++)
{
    stopwatch.Restart();
    foreach (var f in Directory.GetFiles(".", "*.dll"))
        Assembly.LoadFrom(f);
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

输出: 34 0 0 0

因此,可以在进行任何反射搜索之前潜在地运行该代码。


0
几年后...
以下是对我有效的从DLL获取属性值的方法。在GetType()方法调用中,我必须使用namespace.classname。

Assembly dllAsm = Assembly.LoadFile(@"C:\THE\FULL\PATH\TO\YOUR\DLL\yourdynolib.dll");

Type yourType = dllAsm.GetType("YourNamespace.YourClassName");
var yourInstance = Activator.CreateInstance(yourType);

PropertyInfo piYourType = null;
piYourType = yourType.GetProperty("YourPropName");

string yourPropValue = (string)piYourType.GetValue(yourInstance);

-1

这并不难。

您可以检查已加载对象的可用函数,如果按名称找到要查找的函数,则窥视其预期参数(如果有)。如果您正在尝试查找调用,则使用MethodInfo对象的Invoke方法调用它。

另一个选项是将外部对象构建为接口,并将加载的对象转换为该接口。如果成功,则本地调用该函数。

这很简单。


哇,不确定为什么会被踩。我已经在生产应用中做了这个功能长达12年了。耸肩如果有人需要一些代码来实现这个功能,请给我发消息。我会打包我的生产代码的部分并发送给你。 - ChrisH
16
我猜测这些反对票与缺乏例子和过于简洁的语气有关...不过看起来你已经有了完整答案的基础,所以不要害怕编辑更多细节 :) - Shadow
6
说“这很简单”有点粗鲁,这就是为什么你被踩的原因。 - ABPerson
1
我在6年前并没有表现出无礼或傲慢的态度...语气在文字中无法传递,显然。那只是一种轻松幽默的表达方式...我也非常确定当时我在那里放了一个代码示例的链接,但我不知道它去哪了(假设我真的记得有这个链接)。:\ - ChrisH
我不知道MethodInfo如何工作,但它似乎很有价值。我认为你的答案有可能比当前被接受的答案更好,但需要完善。如果你有时间完成它,我们将不胜感激。如果可以,请不要链接到代码示例。这些可能会在未来失效。最好提供自己的示例,并可能附带源或额外信息以供继续阅读。 - SpaghettiCook
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接