.Net динамически загружать DLL - PullRequest
8 голосов
/ 02 марта 2010

Я пытаюсь написать некоторый код, который позволит мне динамически загружать 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

РЕДАКТИРОВАТЬ: Новый код из примеров Влада:

    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, преобразованный из примера Влада 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)

РЕДАКТИРОВАТЬ: Реализация на основе кода Пола Колера

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

Ответы [ 3 ]

4 голосов
/ 02 марта 2010

Версия 2 - этот пример загружает DLL из текущего каталога. Есть 2 проекта, 1 проект консольного приложения и проект «модуля» (модуль «копирует» свою DLL в рабочий каталог консольного приложения).

Пример ниже просто демонстрирует динамическую загрузку DLL, которая реализует интерфейс. Интерфейс IModule просто сообщает свое имя. PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll") создаст экземпляр любого IModule экземпляра, найденного в DLL в текущем каталоге, оканчивающемся на «.Module.dll». Это отраженная версия VB.NET прямо из Mini SQL Query.

Имея это в виду что-то вроде:

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
3 голосов
/ 02 марта 2010

Определен ли тип 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, ...)

Edit: если 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)
1 голос
/ 02 марта 2010

Несколько вещей, которые нужно искать в вашем коде

  • Выполните отладку и убедитесь, что сборка загружена правильно, на случай, если происходит сбой из-за проверки зависимостей
  • Вместо использования GetType используйте GetExportedType, чтобы у вас было меньшее подмножество для итерации по
  • CreateInstance должен использовать ваш загруженный тип, а не интерфейс (вы не можете создать объект из интерфейса)
  • Лично мне не нравится называть свою переменную задницу, я бы вместо этого сократил ее до ассемблера:)
...