Automatische Erkennung von Subroutinen durch Zeichenketten
-
Freitag, 15. Februar 2013 12:55
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
Alle Antworten
-
Freitag, 15. Februar 2013 13:08
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 18:32
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 21:22
Hallo Bolzen,
Habe mich mal mit beschäftigt, aber wieder verworfen.
Vielleicht hilft Dir das weiter
Gruss Ellen
(VB2008 Express, VB2010 Express, VST2008 professional) Ellens Codegallerie
-
Sonntag, 17. Februar 2013 12:32
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 17:40Beantworter
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
- Bearbeitet Elmar BoyeMVP, Editor Sonntag, 17. Februar 2013 17:41
-
Montag, 18. Februar 2013 15:46
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 20:54
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 Markus Schertler Montag, 18. Februar 2013 20:55
-
Dienstag, 19. Februar 2013 12:55
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 16:34
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:50
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
-
Mittwoch, 20. Februar 2013 17:36
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 18:34Beantworter
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 19:24
Hallo Bolzen,
das System.Windows.Forms.Application.DoEvents() kann man auch weglassen. Dürfte nichts ausmachen.
Grüsse Markus
-
Sonntag, 24. Februar 2013 16:57
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 17:54Beantworter
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 19:48
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 Markus Schertler Sonntag, 24. Februar 2013 19:52
-
Sonntag, 24. Februar 2013 21:12Beantworter
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-insAuch ansonsten gilt: Vorsicht mit Assembly.LoadFrom, siehe
Suzanne Cooks Klassiker: Choosing a Binding Contextund Best Practices für das Laden von Assemblys
(was den ersten Artikel etwas aktualisiert)Gruß Elmar
-
Sonntag, 24. Februar 2013 21:58
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 StringDie 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 Markus Schertler Sonntag, 24. Februar 2013 21:58
-
Montag, 25. Februar 2013 09:28Beantworter
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 10:50
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

