none
Automatische Erkennung von Subroutinen durch Zeichenketten

    Frage

  • Hallo,

    ich arbeite an einem Programm, dass über das Internet kommuniziert. Die Clientsoftware soll aus Zeichenketten, die sie vom Host erhält Aktionen durchführen (wahrscheinlich ein einer Subroutine). Mir geht es nur um das umwandeln der Zeichenkette in eine Aktion. Bis jetzt habe ich das mir einer Select-Case-Struktur gelöst. Das funktioniert gut, ist aber umständlich erweiterbar. Es wäre von großem Vorteil, wenn die Subs, die Ausgeführt werden in einer externen Datei gelagert sind (zum Beispiel vielleicht eine Klassenbibliothek) und dass dann das Programm sich die entsprechende Subroutine sucht und ausführt. Wie kann man das lösen?

    Tut mir leid, mir ist auf die Schnelle kein passender Titel eingefallen.

    Vielen Dank schon im Voraus.


    Gruß, Bolzen

    Freitag, 15. Februar 2013 12:55

Alle Antworten

  • Hi,

    auch wenn ich es nicht für einen wirklich geeigneten Weg halte, wäre Reflection evtl. was für dich.

      http://msdn.microsoft.com/de-de/library/a89hcwhh.aspx


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Freitag, 15. Februar 2013 13:08
    Moderator
  • Hallo Stefan,

    wie wäre denn der geeignete Weg? Vielleicht muss ich ja an der Grundüberlegung noch etwas feilen.


    Gruß, Bolzen

    Freitag, 15. Februar 2013 18:32
  • Hallo Bolzen,

    Aufruf von Methoden per Index

    Habe mich mal mit beschäftigt, aber wieder verworfen.

    Vielleicht hilft Dir das weiter

    Gruss Ellen


    (VB2008 Express, VB2010 Express, VST2008 professional) Ellens Codegallerie

    Freitag, 15. Februar 2013 21:22
  • Hallo,

    erstmal danke für die Antworten. Um meine Frage etwas allgemeiner, aber deutlicher zu machen: Ich möchte mein Programm so gestalten, dass es von außen, also nicht innerhalb der .exe leicht erweiterbar ist. Ich weiß nicht wie das normalerweise bei erweiterbaren Programmen ist, vielleicht kann mir ja jemand etwas genaueres sagen.


    Gruß, Bolzen

    Sonntag, 17. Februar 2013 12:32
  • Hallo Bolzen,

    da gibt es generell zwei Wege:
    Direkt über .NET Erweiterungen wofür sich z. B. das Managed Extensibility Framework (MEF).
    Dabei müssen die (anderen) Programmierer .NET Assemblies bereitstellen, die dann von Deinem Programm genutzt werden.

    Eine andere wäre das Nutzen einer Skriptsprache; für .NET wäre eine Möglichkeit Iron-Phyton, eine andere PowerShell.

    Nur sind beide Wege durchaus anspruchsvoll - noch mehr als das, was ich Ellen vorgeschlagen hatte (und wohl nicht ganz umsetzbar war).

    Wenn Du es etwas kleiner möchtest (vermutlich), wäre es hilfreich, wenn Du konkreter beschreibst, was Deine Erweiterungen leisten sollen - aber etwas konkreter als Select-Case-Struktur gelöst ;)

    Gruß Elmar

    Sonntag, 17. Februar 2013 17:40
    Beantworter
  • Hallo Elmar,

    meine Frage basiert auf dieser Diskussion. Der Client bzw. Host soll die Nachricht, die er über das Internet erhält weiterverarbeiten.

    Es gibt verschiedene Kommandos, die mit den Nachrichten versendet werden können. In der Erweiterung sollen also die "Übersetzungen" der Kommandos verarbeitet und ausgeführt werden.

    Ein Beispiel: Die Nachricht, die empfangen wird ist 'down'. Die Software sucht die entsprechende Subroutine mit dem Namen 'down'. In dieser Sub steht, dass das Fenster jetzt geschlossen werden soll.

    Falls ich mich jetzt ein bisschen schwammig ausgedrückt haben sollte, bitte melden.

    Wenn man eine Skriptsprache verwendet, kann man die dann auch in ein "normales" Programm einbinden. Ich interessiere mich nämlich schon seit längerem für PowerShell, habe mich bis jetzt aber noch nicht eingearbeitet.


    Gruß, Bolzen PS: Ich programmiere mit VS12(Desktop) und VS10

    Montag, 18. Februar 2013 15:46
  • Hallo Bolzen,

    so wie ich es verstehe geht es um den dynamischen Aufruf von Methoden anhand von Strings.
    Wenn Methoden dazu kommen oder sich der Name ändert soll der aufrufende Code nicht geändert werden.

    Im folgenden Beispiel ist eine Solution mit einem Windows Forms Projekt und eine Class Library (ClassLibrary1). Das Windows Forms Projekt hat eine Referenz auf die ClassLibrary1
    mit CopyLocal True, so dass sich die CassLibrary1.dll im Anwendungsverzeichnis befindet.

    In clsDynamicClasses.MethodInvoke ist auch gezeigt wie Parameter an die dynamisch aufgerufene Methode übergeben werden können.

    Option Strict On

    Public Class Form1
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim o As Object

        clsDynamicClasses.CreateObject("ClassLibrary1",
                                       "ClassLibrary1",
                                       "Class1",
                                       o)

        ' Die Methode mit dem Namen Hello1 gibt eine Message Box mit dem Text "Hello 1" aus.
        clsDynamicClasses.MethodInvoke(o, "Hello1")

        ' Die Methode mit dem Namen Hello2 gibt eine Message Box mit dem Text "Hello 2" aus.
        clsDynamicClasses.MethodInvoke(o, "Hello2")
      End Sub
    End Class


    Public Class clsDynamicClasses

      Public Shared Function CreateObject(ByVal pstrAssemblyName As String, _
                                                                 ByVal pstrNamespace As String, _
                                                                 ByVal pstrClsName As String, _
                                                                 ByRef pobjObject_Out As Object) As Boolean

        Dim blnProcRCOK As Boolean = False
        Dim exProcEx As Exception
        Dim strProcExRemark As String = ""

        Dim ObjMethodTyp As Type

        Try
          ' Erst wenn die Methode benötig wird, wird die Assembly geladen.
          strProcExRemark = "Error with Assembly " & vbCrLf & pstrAssemblyName

          Dim asmsloaded() As System.Reflection.Assembly = _
                                            AppDomain.CurrentDomain.GetAssemblies
          Dim asmloaded As System.Reflection.Assembly
          Dim asm As System.Reflection.Assembly

          For Each asmloaded In asmsloaded
            ' Aus einem Forum:
            ' The problem is that you can actually load an assembly multiple times.
            ' Most of the time assemblies are loaded using Assembly.Load()
            ' which will load an assembly only once.  However if  you call Assembly.LoadFile()
            ' then you can
            ' load the same assembly from different paths at the same time.  
            ' Needless to say this can cause problems.  Assembly.LoadFrom()
            ' can also be used but it can cause similar problems.  

            If asmloaded.FullName.StartsWith(pstrAssemblyName) Then
              asm = asmloaded
              Exit For
            End If
          Next

          If asm Is Nothing Then
            asm = System.Reflection.Assembly.Load(pstrAssemblyName)
          End If

          If asm Is Nothing Then
            '  Vor der Benutzung eines Objekts prüfen ob es nicht Nothing ist.
            Throw New ApplicationException("Assembly.Load Method Returned Nothing. Maybe Assembly does not exist.")
          End If

          strProcExRemark = "Error with Method " & vbCrLf & pstrNamespace & "." & pstrClsName

          ObjMethodTyp = asm.GetType(pstrNamespace & "." & pstrClsName, True, True)
          pobjObject_Out = Activator.CreateInstance(ObjMethodTyp)

          strProcExRemark = ""

          blnProcRCOK = True

        Catch ex As System.Exception
          blnProcRCOK = False
          exProcEx = ex
        End Try

        If blnProcRCOK Then
          Return True
        Else
          pobjObject_Out = Nothing
          Throw exProcEx
        End If
      End Function


      Public Shared Function MethodInvoke(ByVal pobjObject As Object, _
                                                                  ByVal pstrMethodName As String, _
                                                                  ByVal ParamArray pobjMethod_Pars() _
                                                                          As Object) As Boolean
        Dim blnProcRCOK As Boolean = False
        Dim exProcEx As Exception
        Dim strProcExRemark As String = ""

        Dim blnOK As Boolean
        Dim ObjMethodTyp As Type
        Dim typMethod As System.Reflection.MethodInfo

        Try
          ObjMethodTyp = pobjObject.GetType

          strProcExRemark = "Error with Method " & vbCrLf & pstrMethodName & "."

          typMethod = ObjMethodTyp.GetMethod(pstrMethodName)

          If typMethod Is Nothing Then
            ' Vor der Benutzung eines Objekts prüfen ob es nicht Nothing ist.
            Throw New ApplicationException("ObjMethodTyp.GetMethod Returned Nothing. Maybe Method does not exist: " & _
                                           pstrMethodName)
          End If

          ' Der eigentliche Prozeduraufruf.
          blnOK = CBool(typMethod.Invoke(pobjObject, pobjMethod_Pars))

          strProcExRemark = ""

          blnProcRCOK = True

        Catch ex As System.Exception
          blnProcRCOK = False

          Dim exToHandle As System.Exception

          If TypeOf ex Is System.Reflection.TargetInvocationException Then
            ' The exception that is thrown by methods invoked through reflection.
            ' This class cannot be inherited.
            exToHandle = ex.InnerException
          Else
            exToHandle = ex
          End If

          exProcEx = New Exception(strProcExRemark & vbCrLf & exToHandle.Message)
        End Try

        ' Ausstehende Ereignisse durchführen lassen.
        System.Windows.Forms.Application.DoEvents()

        If Not blnProcRCOK Then
          Throw exProcEx
        End If
      End Function

    End Class



    Das ClassLiberary1 Class Library Projekt enthält folgende Klasse:

    Option Strict On

    Public Class Class1
      Public Sub Hello1()
        MsgBox("Hello 1")
      End Sub

      Public Sub Hello2()
        MsgBox("Hello 2")
      End Sub
    End Class



    • Bearbeitet Markus222 Montag, 18. Februar 2013 20:55
    Montag, 18. Februar 2013 20:54
  • Wenn du nicht ganz so tief in .NET eindringen willst, und die Subs wirklich nur "Aktionen" machen, aber nichts zurückgeben sollen (also die Interaktion zwischen deinen Subs und dem Hauptprogramm maximal darin besteht, einen Übergabeparameter zu bekommen), dann könnte man das auch einfacher lösen, indem man kleine Programme statt Subs schreibt und den Command-String ausliest. Den Namen deiner EXE kannst du dann in eine Datei schreiben, zusammen mit dem String, der sie auslöst. In deinem Hauptprogramm sollte dann eine Abfrage pauschal alle Paare von Strings und Programmnamen durchlaufen, um zu schauen welcher passt. Das ist dann auch von außen erweiterbar, und es können jederzeit neue Strings und auch neue Programme zugefügt werden, vorausgesetzt sie verarbeiten die Commandline.

    Kommt halt darauf an, wie komplex die beiden Teile (Sub und Hauptprogramm) zusammenarbeiten sollen ...

    LG, Dennis.

    Dienstag, 19. Februar 2013 12:55
  • Hallo Dennis,

    danke für die Antwort, aber ich glaube in meinem Programm kann ich das nicht verwenden, da diese Aktionen Zugriff auf das Programmgeschehen brauchen.


    Gruß, Bolzen PS: Ich programmiere mit VS12(Desktop) und VS10

    Dienstag, 19. Februar 2013 16:34
  • Hallo Markus,

    vielen Dank für deine Antwort. Ich habe das jetzt mal alles kopiert und es funktioniert genau wie ich es haben möchte! Ich versuche gerade noch den Code zu verstehen und melde mich wieder, wenn ich dazu Fragen habe.


    Gruß, Bolzen PS: Ich programmiere mit VS12(Desktop) und VS10


    • Bearbeitet Bolzen Dienstag, 19. Februar 2013 16:51
    Dienstag, 19. Februar 2013 16:50
  • Hallo, jetzt habe ich dann mal eine Frage.

    Wie lässt sich der Code von Markus in WPF übertragen. Ich brauche nur eine "Übersetzung" für 'System.Windows.Forms.Application.DoEvents()' , weil (VS): "Forms" ist kein Member von "Windows".

    Sonst habt ihr mir aber schon um einiges weitergeholfen, danke.


    Gruß, Bolzen PS: Ich programmiere mit VS12(Desktop) und VS10

    Mittwoch, 20. Februar 2013 17:36
  • Hallo Bolzen,

    Application.DoEvents gibt es in WPF nicht - und gibt dort wenig Sinn, da es für die Windows Meldungsschleife ausgelegt ist, die in WPF nur eine geringe Bedeutung hat.

    Verwende anstattdessen den WPF Dispatcher Mechanismus.
    Mehr dazu: Erstellen reaktionsfähigerer Anwendungen mit dem Dispatcher

    Den kann man auch ohne weitere Threads verwenden, in dem man Aufgaben mit niedriger Priorität (Background oder weniger) über BeginInvoke aufruft.

    Gruß Elmar

    Mittwoch, 20. Februar 2013 18:34
    Beantworter
  • Hallo Bolzen,

    das System.Windows.Forms.Application.DoEvents() kann man auch weglassen. Dürfte nichts ausmachen.

    Grüsse Markus

    Mittwoch, 20. Februar 2013 19:24
  • Hallo,

    durch den hilfreichen Code von Markus habe ich jetzt eine Klasse mit Prozeduren, die eine MsgBox zeigen. Aber wie ändert man dann extern den Text einer TextBox oder die Position eines Image?

    Dann habe ich noch eine Frage: Wie funktioniert das mit CopyLocalTrue ? Oder wie kann man eine ClassLibrary mithilfe eines Pfades aufrufen?


    Gruß, Bolzen PS: Ich programmiere mit VS12(Desktop) und VS10



    • Bearbeitet Bolzen Sonntag, 24. Februar 2013 17:12
    Sonntag, 24. Februar 2013 16:57
  • Hallo,

    ein Schritt, aber da kommen dann noch einige mehr bis zu Ziel.

    Was den Zugriff auf Deine Bedienoberfläche angeht, wirst Du eine Schnittstelle definieren müssen, über die Deine externe Klassen zugreifen können. Direkt verdrahten sollte man das nicht, denn wird kannst man jede Änderung an etlichen Stellen vornehmen müssen.

    Die Schnittstelle sollte nicht direkt Steuerelemente veröffentlichen, sondern Methoden, die die Änderungen an der Oberfläche (und wo immer auch sonst noch) vornehmen.

    CopyLocal bestimmt, dass eine Assembly direkt ins Programm-Verzeichnis kopiert wird.

    Finden (irgendwo hinterlegen) und laden musst Du sie schon selbst via Assembly.Load bzw. indem man Activator.CreateInstance einen vollqualifizierten Namen übergibt.

    Zusammengefasst bist Du am Ende dort was ich oben schon erwähnt hatte nämlich MEF.
    Und ich würde Dir eher raten, Dich näher damit zu beschäftigen, denn das kann das alles. Markus Code wäre nur ein sehr schmaler Auschnitt von dem, was man braucht, um eine dauerhaft funktionierende Lösung zusammenzustellen.

    Gruß Elmar

    Sonntag, 24. Februar 2013 17:54
    Beantworter
  • Hallo Bolzen,

    also indem das man Parameter an die Methoden mitgibt. Oder Eigenschaften der Klasse vor Aufruf der Methode setzt die dann von der Methode ausgewertet werden.

    Falls es das nicht ist was du meinst wäre vielleicht ein konkretes Codebeispiel hilfreich in dem erläutert ist was du meinst.

    CopyLocal ist die Standardeinstellung wenn man in einer Solution mit mehreren Projekten eine Referenz von einem Projekt auf ein anderes setzt. In diesem Fall von der Windows Forms Anwendung auf das DLL Projekt ClassLibrary1. Die Dll Datei wird dann automatisch in das Anwendungsverzeichnis kopiert.

    Über einen Pfad ginge mit Assembly.LoadFrom

    Public Class Form1
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim objClass1 As Object
        Dim objClass1_A As ClassLibrary1.Class1

        clsDynamicClasses.CreateObject("ClassLibrary1",
                                       "ClassLibrary1",
                                       "Class1",
                                       objClass1)

        ' Die Methode mit dem Namen Hello1 gibt eine Message Box mit dem Text "Hello 1" aus.
        clsDynamicClasses.MethodInvoke(objClass1, "Hello1")

        ' Das folgende Beispiel gibt Parameter an die Methode Hello2 mit.
        ' Diese werden in der Methode verwendet.
        clsDynamicClasses.MethodInvoke(objClass1, "Hello2", "Das ist Parameter1", 2, Me)

        ' Man kann es auch typsicher machen.
        objClass1_A = DirectCast(objClass1, ClassLibrary1.Class1)

        objClass1_A.Hello1()

        objClass1_A.Hello2("Par1", 1, Me)

        ' Das folgende Beispiel setzt vor dem Methodenaufruf Properties der dynamisch erzeugten
        ' Klasseninstanz.
        objClass1_A.Text = "Guck Guck ..."
        objClass1_A.Hello3()

      End Sub
    End Class



    Das ist der Code in ClassLibrary1.Class1:

    Option Strict On

    Public Class Class1
      Public Sub Hello1()
        MsgBox("Hello 1")
      End Sub

      Public Sub Hello2(ByVal par1 As String,
                        ByVal int1 As Integer,
                        ByVal pobj As Object)
        MsgBox(par1 & vbCrLf & int1.ToString & vbCrLf & pobj.ToString)
      End Sub

      Public Text As String

      Public Sub Hello3()
        MsgBox(Text)
      End Sub

    End Class


    • Bearbeitet Markus222 Sonntag, 24. Februar 2013 19:52
    Sonntag, 24. Februar 2013 19:48
  • Hallo Markus,

    Der Code hat einen entscheidenden Schönheitsfehler, da ClassLibrary1.Class1 im Code referenziert wird, wird die über die Verweise eingebundene Assembly verwendet. Und man könnte sich den Rest sparen.

    Um eine solche Abhängigkeit zu vermeiden braucht man eine Schnittstellen-Assembly, die sowohl vom Programm wie von der unabhängigen (Plugin-)Assembly verwendet wird.

    Ein Beispiel A Simple Plug-In Library For .NET
    und einige mehr http://www.codeproject.com/KB/macros/#Add-ins%2fPlug-ins

    Auch ansonsten gilt: Vorsicht mit Assembly.LoadFrom, siehe
    Suzanne Cooks Klassiker: Choosing a Binding Context

    und Best Practices für das Laden von Assemblys
    (was den ersten Artikel etwas aktualisiert)

    Gruß Elmar

    Sonntag, 24. Februar 2013 21:12
    Beantworter
  • Hallo Elmar,

    du schriebst:

    >  Der Code hat einen entscheidenden Schönheitsfehler,
    > da ClassLibrary1.Class1 im Code referenziert
    > wird, wird die über die Verweise eingebundene Assembly verwendet.
    > Und man könnte sich den Rest sparen.

    Hm, ich weiss nicht. Es ging ja um den Aufruf von Methoden mittels Strings.

    Man könnte ja Hello3 im obigen Beispiel auch so aufrufen.
    Dim strMethodName as String

    Die Variable wird nun auf irgendeine Weise gefüllt.

    clsDynamicClasses.MethodInvoke(objClass1, strMethodName)

    Das Properties der Klasse über eine Objektvariable von deren Typ gesetzt werden halte ich für
    legitim. Dann sind eben alle in Frage kommenden Methoden in dieser Klasse.

    Im übrigen gibt es halt die Möglichkeit die Parameter zu übergeben. Man braucht dann so einen Verweis garnicht.

    Mit LoadFrom habe ich keine Erfahrung. Der im Beispiel verwendete Load funktioniert sehr gut.
    Wenn man ohne Referenz arbeiten möchte kann man auch die Assembly per Hand in das Anwendungsverzeichnis kopieren.


    • Bearbeitet Markus222 Sonntag, 24. Februar 2013 21:58
    Sonntag, 24. Februar 2013 21:58
  • Hallo Markus,

    Verzichtet man auf die konkrete Typen (alles als Object) landest Du bei später Bindung.

    Die Aussage "Methoden mittels Strings" von Bolzen sollte man nicht zu wörtlich interpretieren.

    Damit der Zugriff einen Nutzen hat, braucht man immer auch Daten. Die wiederum befinden sich in Klassen und deren konkrete Inkarnation ist eine Instanz. Worauf wiederum die Methoden auf die eine oder andere Weise Zugriff erlangen müssen.

    Und Möglichkeiten wären Schnittstellen oder Serialisierung (JSON/Xml).

    Kurz: Es ist nicht so ganz einfach, unabhängige Dinge miteinander zu verknüpfen. Und zuerst weniger eine Programmier- als eine Verständnisaufgabe.

    Gruß Elmar

    Montag, 25. Februar 2013 09:28
    Beantworter
  • Hallo Elmar,

    ja das stimmt. Es kommt halt darauf an ob es wirklich sinnvoll ist. Daten kann man dann per Parameter oder besser Parameterobjekten an die aufgerufene Methode übergeben.

    Man definiert eine Basisklasse XYZ-Parameter und leitet davon je nach Verwendungszweck verschiedene Parameterklassen ab. So befinden sich die Daten ebenfalls in Klasseninstanzen die als Parameter übergeben werden und die aufgerufene Methode hat Zugriff darauf.

    Wie gesagt, ich denke so etwas lässt sich auch einfach lösen. Nur ist die Frage ob es wirklich den Aufwand lohnt und Sinn macht.

    Markus

    Montag, 25. Februar 2013 10:50