动态加载DLL的.NET实现

8
我正在尝试编写一些代码,使我能够根据应用程序设置动态加载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?需要使用相同的 DLL。PK - Paul Kohler
请再次检查下面的解决方案... - Paul Kohler
1
它起作用了!我最终使用了你的代码Paul,它完全按照我的需求工作。 对于未来可能遇到这个线程的任何人,我将把我的最终代码添加到原始帖子中,希望他们能比我更容易地让它工作。感谢大家的帮助。 - hermiod
3个回答

4

版本2 - 此示例从当前目录加载DLL。 有两个项目,一个控制台应用程序项目和一个“模块”项目(该模块将其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控制台应用程序”中

' 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

嗨,保罗,我已经尝试实现你的代码了,我的努力已经附加到主帖子中,并附有错误信息,因为在评论中没有足够的空间来容纳它,而且评论中也没有格式。至少现在我得到了一个错误,只是不知道为什么! - hermiod

3

您是否需要加载的DLL中定义了ICRDataLayer类型?如果是这样,您似乎已经在项目设置中引用了该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,并且插件只是实现了该接口,则您需要插件为您提供一个工厂:(对不起,我不熟悉VB.NET的语法,以下是C#代码)

// 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,现在它更加有意义了。我会尽快尝试一下的。 - hermiod
我试了你的代码 Vlad。有趣的是,如果我调试它,会到达“t as type”一行,但不会执行该行后面的任何代码。没有异常,没有错误,它只是停止处理跟随该行的任何代码并弹出窗体。 即使我放入像 MessageBox.Show("你好") 这样简单的东西,也不会被执行。 如我在对 nobugz 的回复中所述,我认为 VS 可能已经损坏了!将尝试使用修复安装,然后再次尝试您的代码。 - hermiod
已成功让VS重新工作,只需将代码移动到按钮单击事件中,现在一切都正常了。奇怪的是,在Dim t As Type=factory.GetType行上我得到了一个“对象引用未设置”的错误,看起来ass.CreateInstance没有工作。有什么想法吗? 我已将当前代码添加到原帖中。 - hermiod
@hermiod:也许你可以在调试器中跳过那段代码,看看CreateInstance会返回什么?obj里会有什么? - Vlad
在执行CreateInstance代码行之后,obj的值为null。 我认为我的VS还有点问题,因为ass.CreateInstance没有返回任何状态信息。 - hermiod
嗯,我想你应该在你的类型名称中添加命名空间。如果我没记错的话,应该像这样CreateInstance("FactoryNamespace.CRDataLayerFactory", True)。 - Vlad

1

在你的代码中要注意以下几个方面:

  • 通过调试来检查程序集是否正确加载,以防由于依赖项检查失败而导致错误
  • 使用GetExportedType而不是GetType,这样可以获得一个更小的子集进行迭代
  • CreateInstance应该使用loadedType而不是接口(无法从接口创建对象)
  • 个人而言,我不喜欢将变量命名为"ass",我会将其缩短为"assem" :)

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