vb.net .Net 动态加载 DLL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2359918/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 14:44:26  来源:igfitidea点击:

.Net Dynamically Load DLL

vb.netreflectiondllassemblies

提问by hermiod

I am trying to write some code that will allow me to dynamically load DLLs into my application, depending on an application setting. The idea is that the database to be accessed is set in the application settings and then this loads the appropriate DLL and assigns it to an instance of an interface for my application to access.

我正在尝试编写一些代码,允许我根据应用程序设置将 DLL 动态加载到我的应用程序中。这个想法是在应用程序设置中设置要访问的数据库,然后加载适当的 DLL 并将其分配给我的应用程序访问的接口实例。

This is my code at the moment:

这是我目前的代码:

        Dim SQLDataSource As ICRDataLayer
    Dim ass As Assembly = Assembly. _
    LoadFrom("M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\SQLServer.dll")

    Dim obj As Object = ass.CreateInstance(GetType(ICRDataLayer).ToString, True)
    SQLDataSource = DirectCast(obj, ICRDataLayer)

    MsgBox(SQLDataSource.ModuleName & vbNewLine & SQLDataSource.ModuleDescription)

I have my interface (ICRDataLayer) and the SQLServer.dll contains an implementation of this interface. I just want to load the assembly and assign it to the SQLDataSource object.

我有我的接口 (ICRDataLayer) 并且 SQLServer.dll 包含此接口的实现。我只想加载程序集并将其分配给 SQLDataSource 对象。

The above code just doesn't work. There are no exceptions thrown, even the Msgbox doesn't appear. I would've expected at least the messagebox appearing with nothing in it, but even this doesn't happen!

上面的代码不起作用。没有抛出异常,甚至没有出现 Msgbox。我原以为至少会出现没有任何内容的消息框,但即使这样也不会发生!

Is there a way to determine if the loaded assembly implements a specific interface. I tried the below but this also doesn't seem to do anything!

有没有办法确定加载的程序集是否实现了特定的接口。我尝试了以下,但这似乎也没有做任何事情!

        For Each loadedType As Type In ass.GetTypes
        If GetType(ICRDataLayer).IsAssignableFrom(loadedType) Then
            Dim obj1 As Object = ass.CreateInstance(GetType(ICRDataLayer).ToString, True)
            SQLDataSource = DirectCast(obj1, ICRDataLayer)
        End If
    Next


EDIT: New code from Vlad's examples:

编辑:来自 Vlad 示例的新代码:

    Module CRDataLayerFactory
    Sub New()
    End Sub
    ' class name is a contract,
    ' should be the same for all plugins
    Private Function Create() As ICRDataLayer
        Return New SQLServer()
    End Function
End Module

Above is Module in each DLL, converted from Vlad's C# example.

以上是每个 DLL 中的模块,从 Vlad 的 C# 示例转换而来。

Below is my code to bring in the DLL:

下面是我引入 DLL 的代码:

Dim SQLDataSource As ICRDataLayer
    Dim ass As Assembly = Assembly. _
    LoadFrom("M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\SQLServer.dll")

    Dim factory As Object = ass.CreateInstance("CRDataLayerFactory", True)
    Dim t As Type = factory.GetType
    Dim method As MethodInfo = t.GetMethod("Create")
    Dim obj As Object = method.Invoke(factory, Nothing)
    SQLDataSource = DirectCast(obj, ICRDataLayer)


EDIT: Implementation based on Paul Kohler's code

编辑:基于保罗科勒代码的实现

Dim file As String
        For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
            Dim assemblyType As System.Type
            For Each assemblyType In Assembly.LoadFrom(file).GetTypes

                Dim s As System.Type() = assemblyType.GetInterfaces
                For Each ty As System.Type In s

                    If ty.Name.Contains("ICRDataLayer") Then
                        MsgBox(ty.Name)
                        plugin = DirectCast(Activator.CreateInstance(assemblyType), ICRDataLayer)
                        MessageBox.Show(plugin.ModuleName)
                    End If
                Next

I get the following error with this code:

这段代码出现以下错误:

Unable to cast object of type 'SQLServer.CRDataSource.SQLServer' to type 'DynamicAssemblyLoading.ICRDataLayer'.

无法将“SQLServer.CRDataSource.SQLServer”类型的对象转换为“DynamicAssemblyLoading.ICRDataLayer”类型。

The actual DLL is in a different project called SQLServer in the same solution as my implementation code. CRDataSource is a namespace and SQLServer is the actual class name of the DLL. The SQLServer class implements ICRDataLayer, so I don't understand why it wouldn't be able to cast it. Is the naming significant here, I wouldn't have thought it would be.

实际的 DLL 位于与我的实现代码相同的解决方案中名为 SQLServer 的不同项目中。CRDataSource 是一个命名空间,SQLServer 是 DLL 的实际类名。SQLServer 类实现了 ICRDataLayer,所以我不明白为什么它不能转换它。这里的命名重要吗,我没想到会是。



Final Working code

最终工作代码

Contents of PluginUtility:

PluginUtility 的内容:

enter code here    Public Shared Function GetInstances1(Of Type)(ByVal baseDir As String, ByVal searchPattern As String) As System.Type()
    Dim tmpInstances As New List(Of Type)
    Try
        Dim file As String
        For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
            Dim assemblyType As System.Type
            For Each assemblyType In Assembly.LoadFrom(file).GetTypes

                Dim s As System.Type() = assemblyType.GetInterfaces
                Return s.ToArray()

            Next
        Next
    Catch exp As TargetInvocationException
        If (Not exp.InnerException Is Nothing) Then
            Throw exp.InnerException
        End If
    End Try
End Function

Code to load the DLL:

加载DLL的代码:

enter code here
    Dim basedir As String = "M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\"
    Dim searchPattern As String = "*SQL*.dll"
    Dim plugin As CRDataLayer.ICRDataLayer

    Try
        Dim file As String
        For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
            Dim assemblyType As System.Type
            For Each assemblyType In Assembly.LoadFrom(file).GetExportedTypes

                If assemblyType.GetInterface("CRDataLayer.ICRDataLayer") IsNot Nothing Then
                    plugin = DirectCast(Activator.CreateInstance(assemblyType), CRDataLayer.ICRDataLayer)
                    MessageBox.Show(plugin.ModuleDescription)
                End If

            Next
        Next
    Catch exp As TargetInvocationException
        If (Not exp.InnerException Is Nothing) Then
            Throw exp.InnerException
        End If
    Catch ex As Exception
        MsgBox(ex.Message)
        Clipboard.SetText(ex.Message)
    End Try

采纳答案by Paul Kohler

Version 2 - This sample loads up a DLL from it current directory. There are 2 projects, 1 console application project and a "module" project (the module 'coppies' its DLL to the working directory of the console app).

版本 2 - 此示例从当前目录加载 DLL。有 2 个项目,1 个控制台应用程序项目和一个“模块”项目(模块“复制”其 DLL 到控制台应用程序的工作目录)。

The sample below simply demonstrates dynamically loading a DLL that implements an interface. The IModuleinterface just reports its name. PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll")will create an instance of any IModuleinstance found in a DLL within the current directory ending with ".Module.dll". It's a reflected VB.NET version straight out of Mini SQL Query.

下面的示例简单地演示了动态加载实现接口的 DLL。该IModule接口仅报告其名称。PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll")将创建IModule在当前目录中以“.Module.dll”结尾的 DLL 中找到的任何实例的实例。它是直接从 Mini SQL Query 中反映的 VB.NET 版本。

With that in mind something like:

考虑到这一点,例如:

Dim modules As IModule() = PlugInUtility.GetInstances(Of ICRDataLayer)(Environment.CurrentDirectory, "*.Server.dll")

Should satisfy your requirement. Then you just need to chose which one to execute!

应该能满足你的要求。然后你只需要选择执行哪一个!

The code:

编码:

In "VB.LoaderDemo Colsole App"

在“VB.LoaderDemo Colsole App”中

' IModule.vb
Public Interface IModule
    Property ModuleName() As String
End Interface

' PlugInUtility.vb
Imports System.IO
Imports System.Reflection
Public Class PlugInUtility
    Public Shared Function GetInstances(Of T)(ByVal baseDir As String, ByVal searchPattern As String) As T()
        Dim tmpInstances As New List(Of T)
        Try
            Dim file As String
            For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
                Dim assemblyType As Type
                For Each assemblyType In Assembly.LoadFrom(file).GetTypes()
                    If (Not assemblyType.GetInterface(GetType(T).FullName) Is Nothing) Then
                        tmpInstances.Add(DirectCast(Activator.CreateInstance(assemblyType), T))
                    End If
                Next
            Next
        Catch exp As TargetInvocationException
            If (Not exp.InnerException Is Nothing) Then
                Throw exp.InnerException
            End If
        End Try
        Return tmpInstances.ToArray()
    End Function
End Class

' MainModule.vb
Module MainModule
    Sub Main()
        Dim plugins As IModule() = PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll")
        Dim m As IModule
        For Each m In plugins
            Console.WriteLine(m.ModuleName)
        Next
    End Sub
End Module

In "Sample1 DLL"(references 'VB.LoaderDemo' for IModule)

在“Sample1 DLL”中(IModule 的参考“VB.LoaderDemo”)

Imports VB.LoaderDemo

Public Class MyModule1
    Implements IModule

    Dim _name As String

    Public Sub New()
        _name = "Sample 1, Module 1"
    End Sub

    Public Property ModuleName() As String Implements IModule.ModuleName
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property

End Class

The output is:

输出是:

> Sample 1, Module 1

回答by Vlad

Is the type ICRDataLayerdefined in the DLL you are going to load? If so, you seem to already reference the DLL in your project settings.

ICRDataLayer您要加载的 DLL 中是否定义了类型?如果是这样,您似乎已经在项目设置中引用了 DLL。

You need to work with just reflection:

您只需要使用反射即可:

Dim obj As Object = ass.CreateInstance("ICRDataLayer", True)
Dim t as Type = obj.GetType()
Dim method as MethodInfo = t.GetMethod("DoSomething")
method.Invoke(obj, ...)


Edit: If ICRDataLayeris implemented in the application, and the plugin just implements the interface, you need the plugin to provide a factory for you: (sorry for C# code, I am not familiar with VB.NET's syntax)

编辑:如果ICRDataLayer在应用程序中实现,而插件只是实现了接口,则需要插件为您提供一个工厂:(对不起C#代码,我不熟悉VB.NET的语法)

// in each of plugins:
static class CRDataLayerFactory // class name is a contract,
{                               // should be the same for all plugins
    static ICRDataLayer Create()
    {
        return new CRDataLayerImplementation();
    }
}

The application's code should look like this:

应用程序的代码应如下所示:

Dim factory As Object = ass.CreateInstance("CRDataLayerFactory", True)
Dim t as Type = factory.GetType()
Dim method as MethodInfo = t.GetMethod("Create")
Dim obj as Object = method.Invoke(factory, null)

SQLDataSource = DirectCast(obj, ICRDataLayer)

回答by Fadrian Sudaman

A few things to look for in your code

在您的代码中需要注意的一些事项

  • Debug through and check that the assembly is loaded correctly, in case it fails due to dependency checking
  • Instead of using GetType, use GetExportedType so you have smaller subset to iterate through
  • The CreateInstance should use your loadedType rather than the interface (you cant create object from an interface)
  • Personally, I dont like naming my variable ass, I would shorten it to assem instead :)
  • 调试并检查程序集是否正确加载,以防因依赖项检查而失败
  • 使用 GetExportedType 而不是使用 GetType,这样您就有更小的子集来迭代
  • CreateInstance 应该使用您的加载类型而不是接口(您不能从接口创建对象)
  • 就个人而言,我不喜欢将我的变量命名为 ass,我会将其缩短为 assem :)