2010-03-01 8 views
6

J'essaye d'écrire du code qui me permettra de charger dynamiquement des DLLs dans mon application, selon un arrangement d'application. L'idée est que la base de données à accéder est définie dans les paramètres de l'application, puis cela charge la DLL appropriée et l'affecte à une instance d'une interface pour mon application à accéder..Net dynamiquement charger DLL

Ceci est mon code au 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) 

J'ai mon interface (ICRDataLayer) et le SQLServer.dll contient une implémentation de cette interface. Je veux juste charger l'assembly et l'assigner à l'objet SQLDataSource.

Le code ci-dessus ne fonctionne tout simplement pas. Aucune exception n'est levée, même la Msgbox n'apparaît pas. Je m'attendais au moins à ce que la boîte de message apparaisse avec rien dedans, mais même cela n'arrive pas!

Existe-t-il un moyen de déterminer si l'assembly chargé implémente une interface spécifique. J'ai essayé le ci-dessous mais cela ne semble pas non plus faire quoi que ce soit!

 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: Un nouveau code à partir des exemples de 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 

Ci-dessus du module dans chaque DLL, converti de l'exemple C# de Vlad.

ci-dessous est mon code pour apporter la 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: Mise en œuvre basée sur le code de 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 

je reçois l'erreur suivante avec ce code: Impossible de diffuser l'objet de type SQLServer.CR

DataSource.SQLServer 'pour taper' DynamicAssemblyLoading.ICRDataLayer '.

La DLL réelle est dans un projet différent appelé SQLServer dans la même solution que mon code d'implémentation. CRDataSource est un espace de noms et SQLServer est le nom de classe réel de la DLL. La classe SQLServer implémente ICRDataLayer, donc je ne comprends pas pourquoi elle ne pourrait pas la convertir. Le nom est significatif ici, je n'aurais pas pensé que ce serait.


Code de travail final

Contenu de 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 pour charger la 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 
+0

Ne pas utiliser 'ty.Name.Contains ("ICRDataLayer")' - vérifier qu'il "implémente le type" avec 'assemblyType.GetInterface ("votre espace de noms etc.ICRDataLayer") '(voirhttp: //msdn.microsoft.com/en-us/library/tcctb9t8.aspx) il vous donnera' Nothing' s'il n'est pas implémenté par le type en cours.En outre, l'exception semble indiquer que l'interface n'est pas implémentée, utilisez-vous une DLL partagée pour cela - elle doit être la même. PK –

+0

Vérifiez à nouveau la solution ci-dessous ... –

+1

Cela fonctionne! J'ai fini par utiliser votre code Paul, il fonctionne exactement comme j'en ai besoin. Pour tous ceux qui pourraient rencontrer ce fil dans le futur, j'ajouterai mon code final dans le post original, donc j'espère qu'ils auront plus de facilité à le faire fonctionner que moi. Merci à tous pour votre aide – hermiod

Répondre

2

Version 2 - Cet exemple charge une DLL à partir du répertoire courant. Il y a 2 projets, 1 projet d'application console et un projet "module" (le module 'coppies' sa DLL dans le répertoire de travail de l'application console).

L'exemple ci-dessous illustre simplement le chargement dynamique d'une DLL qui implémente une interface. L'interface IModule signale simplement son nom. PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll") va créer une instance de n'importe quelle instance IModule trouvée dans une DLL dans le répertoire courant se terminant par ".Module.dll". C'est une version reflétée de VB.NET directement issue de Mini SQL Query.

Avec cela à l'esprit quelque chose comme:

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

devrait satisfaire vos besoins. Ensuite, vous avez juste besoin de choisir celui à exécuter!

Le code:

Dans "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 

Dans "Sample1 DLL" (références 'VB.LoaderDemo' pour 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 

La sortie est:

> Sample 1, Module 1 
+0

Salut Paul, J'ai essayé d'implémenter votre code, mon effort a été attaché au message principal avec des informations d'erreur car il n'y a pas assez de place pour l'insérer dans un commentaire et vous n'obtenez pas non plus de formatage dans un commentaire. Au moins, je reçois une erreur maintenant, je ne sais pas pourquoi! – hermiod

+0

A fait une réécriture complète ... –

3

est le type ICRDataLayer défini dans la DLL que vous êtes va charger? Si c'est le cas, vous semblez déjà référencer la DLL dans les paramètres de votre projet.

Vous devez travailler avec la réflexion juste:

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: Si ICRDataLayer est implémenté dans l'application et le plug-in implémente simplement l'interface, vous avez besoin du plug-in pour fournir une usine pour vous : (désolé pour le code C#, je ne suis pas familier avec la syntaxe de 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(); 
    } 
} 

code de l'application devrait ressembler à ceci:

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) 
+0

Merci pour ce code Vlad, il fait beaucoup plus de sens maintenant. Je vais essayer quand je peux. – hermiod

+0

@hermiod: vous êtes les bienvenus – Vlad

+0

J'ai essayé votre code Vlad. Fait intéressant, si je débogue, il arrive à la ligne 't comme type' et n'exécute aucun code après lui. Aucune exception, aucune erreur, il arrête simplement de traiter tout code qui suit cette ligne et affiche le formulaire. Même si je mets quelque chose de simple comme MessageBox.Show ("Hello"), il n'est pas exécuté. Comme indiqué dans ma réponse à nobugz, je pense que VS peut être cassé! J'essaierai une installation de réparation, puis réessayerai votre code. – hermiod

1

Quelques choses à rechercher dans votre code

  • Debug par et vérifier que l'ensemble est correctement chargé, en cas elle échoue en raison de la dépendance de vérification
  • Au lieu d'utiliser GetType, utilisez GetExportedType afin de disposer d'un sous-ensemble plus petit pour itérer
  • CreateInstance doit utiliser votre loadedType plutôt que l'interface (vous ne pouvez pas créer d'objet à partir d'une interface)
  • Personnellement, je n'aime pas nommer mon cul variable, je le raccourcirais pour assem à la place :)
Questions connexes