Irrlicht.dll - DLL Call

  • Hallo, ich will mich mit Irrlicht beschäftigen. Ich weiß das es eine UDF dafür gibt, allerdings liegt dort eine weitere DLL bei, worauf diese UDF zugreift.
    Ich selber möchte direkt die Irrlicht.dll verwenden ohne Umwege über den Wrapper.

    Nun stocke ich aber bei einem Dll Call. Und zwar arbeite ich gerade dieses Tutorial durch: Tutorial 1: HelloWorld
    Mein derzeitiger Code sieht so aus:

    [autoit]

    Global $hIrrlicht, $IrrlichtDevice

    [/autoit][autoit][/autoit][autoit][/autoit][autoit][/autoit][autoit]

    $hIrrlicht = DllOpen('Irrlicht.dll')

    [/autoit][autoit][/autoit][autoit]

    $irrlichtDevice = DllCall($hIrrlicht, 'ptr', 'createDevice', _
    'E_DRIVER_TYPE', 'EDT_SOFTWARE', _
    'dimension2d<u32>', 'dimension2d<u32>(640, 480)', _
    'u32', 16, _
    'bool', false, _
    'bool', false, _
    'bool', false, _
    'IEventReceiver *', 0)

    [/autoit][autoit][/autoit][autoit]

    If @error Then MsgBox(0, '', 'Error: ' & @error, 5)

    [/autoit][autoit][/autoit][autoit]

    While 1
    WEnd

    [/autoit][autoit][/autoit][autoit]

    DllClose($hIrrlicht)

    [/autoit]

    Ich weiß das ich DllCall total falsch verwende, aber bevor ich jetzt darauf näher eingehe solltet ihr euch kurz die Referenz dazu ansehen: createDevice()

    Nun sollte ein Fenster erscheinen. Tut's aber nicht. Und zwar sieht man in der Referenz das ein "eigener Datentyp" verwendet wird. Kurz: (ich glaube so heißen die) eine Klasse. Wie kann ich dies nun als "Type" eintragen?

    Ich hoffe ihr könnt mir weiterhelfen diesen DllCall so richtig zu stellen, dass dieser die gewünschte Funktion ausführt. Ich rätsel da jetzt schon seit gestern! ^^


    €dit:
    Weil nicht jeder die Irrlicht.dll hat, habe ich das oben genannte Script sowie die DLL in diese *.zip ausgelegt: Irrlicht.zip

    4 Mal editiert, zuletzt von Yjuq (19. Februar 2013 um 16:32)

  • Ich denke, das wird so nichts. In ganz Irrlicht gibt es nämlich nur zwei normal exportierte Funktionen (createDevice und createDeviceEx), die man von außerhalb verwenden sollte.
    Diese Funktionen erzeugen beide eine IrrlichtDevice, das ist ein Interface, also ein Pointer auf eine abstrakte Klasse.
    Als Datentyp kannst du einfach einen pointer nehmen, aber den kannst du nicht einfach weiterverwenden.

    Man kann das mit inline-assembler machen, das habe ich auch schon einmal ausprobiert (geht nur mit dlls, die mit VC++ compiled wurden):
    Der Pointer zeigt auf einen Speicherblock. Darin sind alle Membervariablen, also alle lokalen Variablen des Objekts enthalten, man sollte aber nicht direkt darauf zugreifen, sondern nur über Methoden (Memberfunktionen). Dafür ist das erst DWORD wichtig, das ist nämlich ein pointer auf die sogenannte vtable. In der vtable sind die Addressen aller Funktionen aufgelistet, einfach der Reihe nach.
    Das sind aber details, die man normalerweise nur beim Compilerbau beachten sollte, denn wenn z.B. in einer neuen Compilerversion oder Irrlichtversion die Reihenfolge der Methoden geändert wird, dann wiord auf die falschen zugegriffen.


    EDIT: Ich denke, ich sollte auch mal auf die eigentliche Frage antworten. E_DRIVER_TYPE ist ein enum, also einfach ein 32 bit integer, dimension2d<u32> besteht auf zwei 32 bit unsigned integer (ulong), u32 ist ein 32-bit integer (ulong), IEventReceiver ein pointer auf eine Klasse, der aber optional ist (auch 0 sein kann).

  • Spoiler anzeigen
    [autoit]

    Global $hIrrlicht, $IrrlichtDevice

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $hIrrlicht = DllOpen('Irrlicht.dll')

    [/autoit] [autoit][/autoit] [autoit]

    $WndDimension = DllStructCreate("DWORD;DWORD")
    DllStructSetData($WndDimension, 1, 640)
    DllStructSetData($WndDimension, 2, 480)
    $irrlichtDevice = DllCall($hIrrlicht, 'ptr:cdecl', 'createDevice', _
    'DWORD', 4, _ ;Direct3D 9
    'ptr', DllStructGetPtr($WndDimension), _
    'DWORD', 16, _
    'BOOLEAN', false, _
    'BOOLEAN', false, _
    'BOOLEAN', false, _
    'ptr', 0)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, '', 'Error: ' & @error, 5)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    DllClose($hIrrlicht)

    [/autoit]


    Ein Fenster wird zwar auf diese Weise erstellt, aber die Anwendung crasht trotzdem.
    Hoffe es irgendeinen Nutzen für dich.

    PS: Eine Referenz, gekennzeichnet durch "&", auf einen Typ, ist stets ein Zeiger auf diesen.

  • Cool danke :D
    Ne, die Anwendung crasht bei mir net ;P

    Läuft wie geschmiert ^^

    Aber eines solltest du mir noch erklären: DllCall der 2te Parameter. Wioeso ptr:cdecl?

  • Sorry, leider muss ich euch nochmal in anspruch nehmen ...
    Ich hänge immernoch am ersten Tutorial: Tutorial 1: HelloWorld

    Mein derzeitiges Script sieht so aus:

    Spoiler anzeigen
    [autoit]

    Opt('MustDeclareVars', 1)

    [/autoit] [autoit][/autoit] [autoit]

    HotKeySet('{ESC}', '_Exit')

    [/autoit] [autoit][/autoit] [autoit]

    Global $hIrrlicht = DllOpen('Irrlicht.dll'), _
    $tdimension2d = DllStructCreate('UINT; UINT'), _
    $ptrdevice

    [/autoit] [autoit][/autoit] [autoit]

    Global Enum $EDT_NULL, $EDT_SOFTWARE, $EDT_BURNINGSVIDEO, $EDT_DIRECT3D8, $EDT_DIRECT3D9, $EDT_OPENGL, $EDT_COUNT

    [/autoit] [autoit][/autoit] [autoit]

    DllStructSetData($tdimension2d, 1, 640)
    DllStructSetData($tdimension2d, 2, 480)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $ptrdevice = DllCall($hIrrlicht, 'ptr:cdecl', 'createDevice', _
    'UINT', $EDT_DIRECT3D9, _
    'ptr', DllStructGetPtr($tdimension2d), _
    'UINT', 16, _
    'BOOL', False, _
    'BOOL', False, _
    'BOOL', False, _
    'ptr', 0)

    [/autoit] [autoit][/autoit] [autoit]

    If @error Then _Exit()

    [/autoit] [autoit][/autoit] [autoit]

    ; WinSetTitle('', '', 'Tutorial 1: HelloWorld')

    [/autoit] [autoit][/autoit] [autoit]

    While Sleep(100)
    WEnd

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _Exit()
    $tdimension2d = 0
    DllClose($hIrrlicht)
    Exit
    EndFunc

    [/autoit]

    Und zwar hänge ich an der Stelle, wo es darum geht den Titel für das Fenster zu setzen. Wie man sieht geht es auch mit WinSetTitle (Zeile 27) aber ich würde es viel lieber über die DLL machen! :D
    Das Problem ist, das dies eine Art Pointer benötigt. Z.B. Des Fensters oder so ähnlich. Sicher bin ich mir aber nicht! ^^

    Hier der Link zu setWindowCaption: setWindowCaption

    Das ganze ist ein wenig schwer zu erklären, daher habe ich mal einen kurzen C++ ausschnitt abgetippt (Ob der richtig ist, ist jetzt nebensächlich) um euch das zu verdeutlichen wieso es bei mir gerade hängt:

    Spoiler anzeigen
    Code
    void main()
    {
    	IrrlichtDevice *device = createDevice(Parameter blabla)
    
    	if (!device)
    		return 1;
    
    	device -> setWindowCaption(L"Hello World! - Irrlicht Engine Demo")
    }

    Hier können glaube ich nur die C++ experten was dazu sagen. Aber über jede Hilfe wäre ich dankbar! ^^
    Diese "device->setWindowCaption()" ist ein Zeiger. Sprich: setWindowCaotion() bezieht sich auf das Objekt device.
    Soviel ich darüber weiß! :P

    Leider habe ich keine Idee wie ich das umsetzen kann. Wäre schön wenn ihr mir da weiterhelfen könntet, weil sich das Restliche Tutorial so auf dieses Verfahren bezieht.

  • Wenn ich das richtig verstehe, ist setWindowCaption eine Memberfunktion der Klasse IrrlichtDevice.
    "device" ist eine Instanz dieser Klasse.
    Ich hab mal nach "autoit dll member function" gegoogelt und das hier gefunden:
    http://www.autoitscript.com/forum/topic/19…-how-in-autoit/
    Wichtig ist hier die erste Antwort von "Valik".

    Zitat


    AutoIt doesn't support using an exported class...


    Also mit AutoIt allein ist das nicht machbar, da muss eine extra .dll gebaut werden.

    Wer andern eine Bratwurst brät
    der hat ein Bratwurstbratgerät.

  • Okay, also kann ich das mit dem ansprechen der Irrlicht.dll direkt lassen.
    Dann muss ich doch auf die au3 + der entsprechenden DLL zurück greifen.

    Dann bedanke ich mich herzlichst! Falls doch jemanden was einfällt, lasst es mich bitte wissen ^^

  • Auf die Gefahr hinaus mir eine E-Watsche (= Ohrfeige) einzufangen:

    Seit AutoIt die Funktion DllCallAddress() anbietet ist es recht simpel auf Interfaces direkt zuzugreifen (du musst aber die Calling Conventions einhalten).

    Spoiler anzeigen
    [autoit][/autoit] [autoit][/autoit] [autoit]

    ;~ #include
    Global Const $PointerSize = __PointerSize__()
    Global Const $tagIDirect3D9 = "ptr QueryInterface;ptr AddRef;ptr Release;ptr RegisterSoftwareDevice;ptr GetAdapterCount;ptr GetAdapterIdentifier;" & _
    "ptr GetAdapterModeCount;ptr EnumAdapterModes;ptr GetAdapterDisplayMode;ptr CheckDeviceType;ptr CheckDeviceFormat;" & _
    "ptr CheckDeviceMultiSampleType;ptr CheckDepthStencilMatch;ptr CheckDeviceFormatConversion;ptr GetDeviceCaps;ptr GetAdapterMonitor;" & _
    "ptr CreateDevice;"
    Func _IFace_GetVTable(Const $pClass)
    If( Not $pClass ) Then Return( SetError(1, 0, 0) )

    [/autoit] [autoit][/autoit] [autoit]

    Local $vtbl = DllStructCreate("ptr", $pClass)
    Local $ret = DllStructGetData($vtbl, 1)
    $vtbl = 0
    Return( $ret )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _IFace_GetVMethodByIndex(Const $pClass, $vtbl, Const $indexZeroBased)
    If( $vtbl == 0 ) Then $vtbl = _IFace_GetVTable($pClass)
    If( Not $vtbl ) Then Return( SetError(1, 0, 0) )

    [/autoit] [autoit][/autoit] [autoit]

    Local $method = DllStructCreate("ptr", $vtbl + ($indexZeroBased * $PointerSize))
    Local $ret = DllStructGetData($method, 1)
    $method = 0
    Return( $ret )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func __PointerSize__()
    Local $t = DllStructCreate("ptr")
    Local $ret = DllStructGetSize($t)
    $t = 0
    Return( $ret )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _IFace_GetNumMethods(Const ByRef $tagIFace)
    If( Not IsString($tagIFace) ) Then Return( SetError(1, 0, 0) )
    Local $iPos = StringInStr($tagIFace, "ptr", 1)
    Local $nOccurences = 0
    While( $iPos )
    $nOccurences += 1

    [/autoit] [autoit][/autoit] [autoit]

    $iPos = StringInStr($tagIFace, "ptr", 1, 1, $iPos + 3)
    WEnd
    Return( $nOccurences )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;
    ;
    ; $array[0] = interface pointer
    ; $array[1] = number of methods
    ; $array[2] = first method's address
    ; $array[n + 1] = n-th method's address ( [1 + 1] = 2 == first method's address )
    ;
    Func _IFaceToArray(Const $pIFace, Const ByRef $tagIFace, ByRef $array)
    Local $nMethods = _IFace_GetNumMethods($tagIFace)

    [/autoit] [autoit][/autoit] [autoit]

    ReDim $array[$nMethods + 2]
    $array[0] = $pIFace
    $array[1] = $nMethods
    If( $nMethods == 0 ) Then Return( SetError(1, 0, 0) )

    [/autoit] [autoit][/autoit] [autoit]

    Local $vtbl = _IFace_GetVTable($pIFace)
    For $i = 1 To $nMethods Step +1
    $array[$i + 1] = _IFace_GetVMethodByIndex($pIFace, $vtbl, $i - 1) ;$index has to be zero-based.
    Next

    [/autoit] [autoit][/autoit] [autoit]

    SetError(0)
    Return( 1 )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Global $g_hD3D9 = -1
    $g_hD3D9 = DllOpen("d3d9.dll") ;Must stay open or else you won't be able to access IDirect3D9's v-table.
    Local $pD3D9 = _Direct3DCreate9()
    MsgBox(0, "", "Anzahl der Ausgabemöglichkeiten: " & _IDirect3D9_GetAdapterCount($pD3D9))
    MsgBox(0, "", _IDirect3D9_AddRef($pD3D9))
    MsgBox(0, "", _IDirect3D9_Release($pD3D9))
    MsgBox(0, "", _IDirect3D9_Release($pD3D9))
    DllClose($g_hD3D9)
    Exit( 0 )

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _Direct3DCreate9()
    Local $ret = DllCall($g_hD3D9, "ptr", "Direct3DCreate9", "UINT", 32)
    If( IsArray($ret) ) Then
    _IFaceToArray($ret[0], $tagIDirect3D9, $ret)
    Return( $ret )
    EndIf
    Return( 0 )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _IDirect3D9_Release(Const ByRef $IDirect3D9)
    Local $ret = DllCallAddress("ULONG", $IDirect3D9[4], "ptr", $IDirect3D9[0])
    If( IsArray($ret) ) Then Return( $ret[0] )
    Return( -1 )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _IDirect3D9_AddRef(Const ByRef $IDirect3D9)
    Local $ret = DllCallAddress("ULONG", $IDirect3D9[3], "ptr", $IDirect3D9[0])
    If( IsArray($ret) ) Then Return( $ret[0] )
    Return( -1 )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _IDirect3D9_GetAdapterCount(Const ByRef $IDirect3D9)
    Local $ret = DllCallAddress("ULONG", $IDirect3D9[6], "ptr", $IDirect3D9[0])
    If( IsArray($ret) ) Then Return( $ret[0] )
    Return( -1 )
    EndFunc

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit]

    Die Anwendung der "UDF" ist recht simpel.
    Du erstellst eine Interface Definition im DllStruct Stil, also "$tagIrrlichtDevice = "ptr activateJoysticks; ptr clearSystemMessages; ptr ...".
    Wendest dann _IFaceToArray(), hast deine Methoden, samt Interface Pointer und kannst nun deine Funtkionen darauf anwenden.

    Ein Beispiel für Direct3D9 ist im Spoiler.

    Wichtig ist, dass du deine Methoden (nur virtuelle auflisten, ansonsten funktioniert das System nicht), in genau derselben Reihe aufschreibst wie sie in der Klassendefinition vorkommen, sowie, dass du stets "ptr" vor deine Methoden schreibst.
    Der Nachteil ist allerdings, das das Ganze nur mit virtuellen Methoden klappt.

    Schau dir einfach den Code im Spoiler an, er sollte (hoffentlich) verständlich.

    EDIT: Die Fehlerüberprüfung müsste allerdings noch verbessert werden.

    Einmal editiert, zuletzt von CentuCore (6. August 2013 um 03:34)

  • Mit ein bisschen Glück drücke ich mich verständlich aus.

    Folgende Klassendefinition:

    Spoiler anzeigen

    führt zu folgender Interfacedefinition:

    [autoit]


    Global Const $tagFoo = "ptr Bar2; ptr Bar3; ptr Bar4; ptr Bar5; ptr Bar7;"

    [/autoit]

    Die Interfacedefinition ist "eigene" Syntax und lautet wie folgt:
    ptr [Name-der-n.ten-virtuellen-Methode];
    ptr: Kennzeichnet eine neue virtuelle Methode.
    Name-der-n.ten-virtuellen-Methode: Optional. Dient lediglich der leichteren Lesbarkeit und evtl. Debug-Hilfe.
    ;: Ende einer Methoden Deklaration

    Die Funktion _IFaceToArray() "parst" die Interfacedefinition lediglich um die Anzahl der virtuellen Methoden festzustellen.
    Die Namen der Elemente dienen als Orientierungshilfe.
    Sprich anstelle des obigen Codes könnte die Definition auch so aussehen:

    [autoit]


    Global Const $tagFoo = "ptr; ptr; ptr; ptr; ptr;"

    [/autoit]

    Was ist eine VTable?
    Eine virtuelle Methode ist eine Methode, deren Adresse dem ausführenden Code erst zur Laufzeit bekannt ist. Das klingt dämlich ist, aber sehr nützlich. Unter anderem uns. Um nun herauszufinden, welche Funktion ausgeführt werden soll, also zu welcher Adresse gesprungen werden soll, muss irgendwo eben jene stehen. Und zwar in der VTable.
    Die Adresse der VTable (eigtl. virtual function table) steht, bei Windows-kompatiblen Compilern, stets an erster Stelle des Interfaces.

    Sprich, bei einem 32bit Programm:
    0x0 - Adresse der VTable
    0x4 - Andere Daten des Interfaces/Klasse
    0xN - ...

    Bei einem 64bit Programm, da ein Pointer/Zeiger nun 8 Byte verbraucht:
    0x0 - Adresse der VTable
    0x8 - Andere Daten des Interfaces/Klasse
    0xN - ...


    Aufbau der VTable, 32bit:
    0x0 - Adresse der ersten virtuellen Methode
    0x4 - Adresse der zweiten virtuellen Methode
    (0xN - 1) * 4 - Adresse der N-ten virtuellen Methode


    Aufbau der VTable, 64bit:
    0x0 - Adresse der ersten virtuellen Methode
    0x8 - Adresse der zweiten virtuellen Methode
    (0xN - 1) * 8 - Adresse der N-ten virtuellen Methode

    Der Index der Adressen der Methoden ist null basiert, wie bei einem Array.


    Hierzu ein kleines "Tutorial":

    Spoiler anzeigen
    [autoit]


    Local $pInterface = _GetInterfacePointer() ;Adresse des Interfaces
    Local $tFirstIFaceElement = DllStructCreate("ptr", $pInterface) ;"ptr" ist nun direkt das erste Element an der Adresse von $pInterface = Adresse der VTable
    Local $pVTable = DllStructGetData($tFirstIFaceElement , 1) ;Die VTable ist ja das erste Element.
    Local $nthMethod = DllStructGetData(DllStructCreate("ptr", $pVTable + ($IndexOfMethodZeroBased * $SizeOfPointer)), 1) ;Greife auf die VTable zu indem die DllStruct bereits belegten Speicher (der VTable) verwenden soll.

    [/autoit]


    Nichts anderes als dieses Beispiel macht vorheriger, geposteter Code.

    Ich hoffe das ganze ist nun verständlicher.

    4 Mal editiert, zuletzt von CentuCore (6. August 2013 um 03:51)

  • Das simmt noch nicht ganz, denn wenn die Funktion auf Elemente des Objekts zugreift, dann muss sie wissen, welches Object sie gerade vor sich hat. Dazu muss der this-pointer (zeigt auf aktuelles Objekt) als zusätzlicher Parameter übergeben werden und zwar im ECX Register. Zusätzlich muss man noch die Calling convention beachten und gegebenenfalls als fastcall (Parameter liegen in Registern) aufrufen.
    Es ist auch nicht festgelegt, wie die vtable und die calling conventions genau aufgebaut sind und das kann sich bei jedem Compiler unterscheiden und nach anderen Versionen ändern.

    Die letzten Probleme kann man aber umgehen, indem man einfach eine DLL mit den Standardeinstellungen mit Microsoft Visual C++ 2010 compiled und diese behält.

  • Wie meinst du, dass nicht festgelegt ist wie die Calling Conventions aufgebaut sind?

    Zitat

    Das simmt noch nicht ganz, denn wenn die Funktion auf Elemente des Objekts zugreift, dann muss sie wissen, welches Object sie gerade vor sich hat. Dazu muss der this-pointer (zeigt auf aktuelles Objekt) als zusätzlicher Parameter übergeben werden und zwar im ECX Register


    Halb richtig. Hatte ich vergessen zu erwähnen, danke.
    Du sprichst von einer __thiscall/__fastcall Funktion, du kannst aber auch __stdcall verwenden, dann ist der this Pointer nicht mehr im ECX Register, sondern wird zuletzt auf den Stack gepusht, wie zum Beispiel bei Direct3D, da C-kompatibel.
    Konkret heißt das, dass mit AutoIt nur __stdcall/__cdecl (virtual) Funktionen aufgerufen werden können. (Es seidenn irgendwer schreibt eine spez. UDF für diesen Zweck)

    Zitat

    Es ist auch nicht festgelegt, wie die vtable und die calling conventions genau aufgebaut sind


    Das interessiert mich.
    Wie schauen denn die anderen Versionen der VTable aus.
    Ich habe immer nur von dieser gelesen.