【问题标题】:.Net Dynamically Load DLL.Net 动态加载 DLL
【发布时间】:2011-01-22 12:39:50
【问题描述】:

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

这是我目前的代码:

        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)

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

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

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

        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

编辑:来自 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

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

下面是我引入 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)

编辑:基于 Paul Kohler 的代码实现

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

我收到以下代码错误:

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

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


最终工作代码

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

加载 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

【问题讨论】:

  • 不要使用ty.Name.Contains("ICRDataLayer") - 检查它是否使用assemblyType.GetInterface("your namespace etc.ICRDataLayer")“实现了类型”(参见http://msdn.microsoft.com/en-us/library/tcctb9t8.aspx)如果当前类型没有实现它,会给你Nothing。此外,异常似乎表明接口未实现,您是否为此使用共享 DLL - 它必须是同一个。 PK
  • 再次检查下面的解决方案...
  • 有效!我最终使用了您的代码 Paul,它完全按照我的需要工作。对于将来可能遇到此线程的任何人,我会将我的最终代码添加到原始帖子中,因此希望他们可以比我更容易地让它工作。谢谢大家的帮助

标签: vb.net reflection dll assemblies


【解决方案1】:

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

您只需要使用反射:

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

编辑:如果应用中实现了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();
    }
}

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

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)

【讨论】:

  • 感谢 Vlad 的代码,现在它更有意义了。有空我会试试看。
  • 我试过你的代码 Vlad.有趣的是,如果我进行调试,它会到达“t as type”行,并且不会在其后执行任何代码。没有异常,没有错误,它只是停止处理该行之后的任何代码并显示表单。即使我输入了像 MessageBox.Show("Hello") 这样简单的东西,它也不会被执行。正如我在回复 nobugz 时所说,我认为 VS 可能坏了!将尝试修复安装,然后再次尝试您的代码。
  • 设法让 VS 再次工作,只需将代码移动到按钮单击事件,现在一切都很好。诡异的。我在 Dim t As Type=factory.GetType 行上收到对象引用未设置错误,看起来 ass.CreateInstance 不起作用。有什么想法吗?我已将我当前的代码添加到原始帖子中。
  • @hermiod:也许您可以在调试器中跳过该代码并查看CreateInstance 将返回什么? obj 中的内容
  • 经过 CreateInstance 行后,obj 什么都不是。我认为我的 VS 仍然有点麻烦,因为我没有在 ass.CreateInstance 上获得任何状态信息。
【解决方案2】:

在您的代码中寻找一些东西

  • 调试并检查 程序集已正确加载,以防万一 由于依赖检查而失败
  • 不要使用 GetType,而是使用 GetExportedType,这样您就有更小的子集可以迭代
  • CreateInstance 应该使用您的 loadedType 而不是接口(您不能从接口创建对象)
  • 就我个人而言,我不喜欢将变量命名为 ass,而是将其缩短为 assem :)

【讨论】:

    【解决方案3】:

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

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

    考虑到这一点:

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

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

    代码:

    在“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
    

    在“Sample1 DLL”中(引用 'VB.LoaderDemo' 用于 IModule)

    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
    

    输出是:

    > Sample 1, Module 1
    

    【讨论】:

    • 嗨,保罗,我已经尝试实现您的代码,我的努力已附在主帖中以及错误信息,因为没有足够的空间将其放入评论中并且您没有获得格式在评论中。至少我现在得到一个错误,我只是不知道为什么!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多