Hallo zusammen,
Grob: ich möchte dem User die Möglichkeit geben, innerhalb eines AutoIt-Programms AutoIt-Code anzulegen und auszuführen. Dabei sollen globale Daten und Funktionen des Hauptprogramms genau so einfach verfügbar sein, als hätte der User die Funktion stattdessen vor Programmstart in Scite getippt.
$iSleepTime = 500
$fDoSomething = True
; $sAutoItCode = InputBox("", "Sourcecode eingeben...")
$sAutoItCode = _AutoItCode_Execute( _
& @LF & '' _
& @LF & '_MyMsgBox("SleepTime:" & $iSleepTime)' _
& @LF & '$sText = "schlecht gelaunt."' _
& @LF & 'If $fDoSomething = True Then' _
& @LF & ' $sText = _MyFunction(_MyRandom(85, 95))' _
& @LF & 'EndIf' _
& @LF & '' _
& @LF & 'Func _MyMsgBox($sText)' _
& @LF & ' MsgBox(64, "", $sText)' _
& @LF & 'EndFunc' _
& @LF & '' _
& @LF & 'Func _MyFunction($iThreshold)' _
& @LF & ' For $i = 1 to 100' _
& @LF & ' IF $i <= $iTreshold then ' _
& @LF & ' Sleep(10)' _
& @LF & ' Else' _
& @LF & ' Sleep($iSleepTime) ' _
& @LF & ' EndIf' _
& @LF & '' _
& @LF & ' ToolTip("Counter:" & $i)' _
& @LF & ' Next' _
& @LF & ' Return "Erfolgreich!"' _
& @LF & 'EndFunc' )
Msgbox(0,"", "Die der Code war " & $sText)
Func _MyRandom($iFrom, $iTo)
Return Random($iFrom, $iTo, 1)
EndFunc
Func _AutoItCode_Execute($sSourceCode)
; ... ?
EndFunc
Alles anzeigen
Bevor ich weiter aushole: Es gibt durchaus Alternativen zu so einer Vorgehensweise (DllCallbacks, mehrere Scripte, IPC, andere Sprachen) - die sind mir bekannt. Aber oben gezeigtes Beispiel verdeutlicht so ziemlich genau, was ich suche. Ich wiederhole noch einmal: Dabei sollen globale Daten und Funktionen des Hauptprogramms genau so einfach verfügbar sein, als hätte der User den Code stattdessen vor Programmstart in Scite getippt. Das beinhaltet Schleifen, Globale variablen, Funktionsaufrufe, AutoIt Funktionen, byRef Parameter, DllStructs, - das volle Programm.
Die Gründe dafür sind unterschiedlich und nicht verhandelbar
Spoiler anzeigen
Einer der Hintergründe als Beispiel: u.a. werden vom User zur Laufzeit "Business Objekte" in meinem Programm erstellt (dazu auch noch einmal mehr hier) - dazu gibt es eine grafische Oberfläche. Nachdem ein Business Objekt im Programm erstellt wurde, werden benötigte Files im Hintergrund generiert (XML Definitionen mit Eigenschaften und Methoden, AutoIt Scripte mit Funktionen, etc.). Das "Business Objekt" wird dann mittels AutoItObject instanziiert und kann als "echtes" Objekt in AutoIt verwendet werden.
Damit die im Objekt definierten Methoden (=Funktionen) aber aufgerufen werden können (das neue, frisch generierte Include ist ja noch nicht "eingebunden"), muss das Script prinzipiell neu starten. Die Include-Anweisung für das neue File vorausgesetzt - die wird auch automatisch eingefügt. Dafür gibt es in meiner Applikation einen entsprechenden Workaround (neue Objekte werden "ausgelagert" - intern relativ umständlich.)
Grob funktioniert das in Etwa so - nur zur Veranschaulichung.:
#include <AutoItObject.au3>
#include "AllObjects.au3"
_AutoItObject_Startup()
$oObject = _AutoItObject_Create()
; $aXMLAttributes[] = wird aus Datei gelesen, z.B. ["foo", "bar"]
$sObjectName = 'TheAlmightyObject'
For $i = 0 To UBound($aXMLAttributes) - 1
_AutoItObject_AddProperty($oObject, $aXMLAttributes[$i])
Next
; $aXMLMethods[] = wird aus Datei gelesen, z.B. [["Funktion_1", "MessageBox(0,'','Ich bin Funktion 1 foooooo '"] , ["Funktion_2", "MessageBox(0,'','Ich bin Funktion 2 baaaaar'"]]
For $i = 0 To UBound($aXMLMethods) - 1 ; Neue Datei mit dem Funktioncoding erzeugen
FileWrite(@ScriptDir & '\TheAlmightyObject.au3', "Func " & $aXMLMethods[$i][0] & "()" & @LF & $aXMLMethods[$i][1] & @LF & "EndFunc")
_AutoItObject_AddMethod($oObject, $aXMLMethods[$i][0], "someFunctionNamePrefix" & $aXMLMethods[$i][0])
Next
FileWrite(@ScriptDir & "\AllObjects.au3", @CRLF & "#include "TheAlmightyObject.au3" )
$oObject.foo = 10 ; klappt!
$oObject.Funktion_1() ; klappt erst beim zweiten start des programms, weil TheAlmightyObject.au3 vorher nicht existierte.
Alles anzeigen
Ich habe nun erst einmal folgende Lösung speziell in meiner Applikation erarbeitet:
1) Mittels AutoItObject wird ein "Container" erzeugt - z.b. um Daten zu übergeben oder Callbacks auszuführen. Dieser Container wird mittels _AutoItObject_Register global in Windows bekanntgemacht.
2) Externe Funktion wird generiert (Script per FileWrite angelegt etc.)
3) Im externen Script wird der erzeugte Container "abgeholt" (um auf Daten zuzugreifen, etc.) und ein zweiter Container wird erstellt. Die "generierte" Funktion wird als Methode zu diesem zweiten Container hinzugefügt.
4) Das Hauptprogramm wird benachrichtigt , holt sich diesen zweiten Container, ruft die Methode auf und übergibt sich selbst als Parameter.
FYI Das ganze habe ich jetzt auch noch einmal hier im englischen Forum (inkl. "UDF") beschrieben. Vielleicht ist dieser Workaround ja schon mal für irgendwen hilfreich.
#include <AutoItObject.au3>
_AutoItObject_Startup()
; Container für Datenübergabe, Callbacks, etc.
$oObject_In = _AutoItObject_Create()
_AutoItObject_RegisterObject($oObject_In, 'AutoItTest.SelfMody.In')
_AutoItObject_AddProperty($oObject_In, 'ready')
; Beispiel: Callback-Methode
_AutoItObject_AddMethod($oObject_In,"MessageBox", "MessageBox")
$oObject_In.MessageBox("1) Regular call of function from within the same script")
; Externe Funktion "generieren"
Make_external_function()
; Externe Funktion aufrufen:
Do Sleep(100)
Until $oObject_In.ready
$oObject_Out = ObjGet("AutoItTest.SelfMody.Out")
$oObject_Out.some_external_function("3) call of function in external (generated) Script")
Func MessageBox($oSelf, $sText) MsgBox(0,"",$sText)
EndFunc
Func Make_external_function()
$sSourceCode = '' _
&@LF& '#include <AutoItObject.au3>' _
&@LF& '_AutoItObject_StartUp()' _
&@LF& '$oObject_In = ObjGet("AutoItTest.SelfMody.In")' _
&@LF& '' _
&@LF& '$oObject_Out = _AutoItObject_Create()' _
&@LF& '_AutoItObject_AddMethod($oObject_Out,"some_external_function", "some_external_function")' _
&@LF& '_AutoItObject_RegisterObject($oObject_Out, "AutoItTest.SelfMody.Out")' _ &@LF& '' _
&@LF& '$oObject_In.MessageBox("2) Callback of Function from external script")' _
&@LF& '$oObject_In.ready = True' _
&@LF& '' _
&@LF& 'While Sleep(100)' _
&@LF& 'WEnd' _
&@LF& '' _
&@LF& 'Func some_external_function($oSelf, $vParam)' _
&@LF& ' MsgBox(0,"some_external_function",$vParam)' _
&@LF& ' Exit' _
&@LF& 'EndFunc'
FileDelete(@ScriptDir & '\autoitselfmody.au3')
FileWrite(@ScriptDir & '\autoitselfmody.au3', $sSourceCode)
Run(StringFormat('%s /AutoIt3ExecuteScript "%s"', @AutoItExe, @ScriptDir & '\autoitselfmody.au3'),@ScriptDir)
EndFunc
Alles anzeigen
Nun meine Frage:
Kennt jemand einen einfacheren / besseren Weg um Funktionen zur Laufzeit zu generieren und dabei Daten/Funktionen Scriptübergreifend verfügbar zu machen?
Ich habe auch versucht, Funktionsreferenzen (a la $oVar = _MyCustomFunction) zwischen den Scripten hin und her zu reichen, aber das klappte nicht gut.
Danke für jeden Hinweis,