Arbeiten mit Dll's in AutoIt


Inhalt

Datentypen

AutoIt selbst nimmt uns die Verwaltung von Datentypen ab, es gibt nur einen Typ (Variant), der funktionsgemäß verwendet wird.
Das ist bei der Arbeit mit Dll-Aufrufen anders. Hier ist eine Angabe der verwendeten Datentypen zwingend erforderlich.

Gültige Datentypen

Typ

Details

none

kein Wert (nur gültig für Rückgabetyp - entspricht void in C)

byte

ein 8 Bit integer

ubyte

ein unsigned 8 Bit integer

short

ein 16 Bit integer

ushort

ein unsigned 16 Bit integer

dword

ein unsigned 32 Bit integer

udword

ein unsigned 32 Bit integer

int

ein 32 Bit integer

uint

ein unsigned 32 Bit integer

long

ein 32 Bit integer

ulong

ein unsigned 32 Bit integer

int64

ein 64 Bit integer

uint64

ein unsigned 64 Bit integer

str

ein ANSI String (max. 65536 Zeichen).

wstr

ein UNICODE wide character String (konvertiert zu/von einem ANSI String während des Aufrufs, wenn benötigt). Max. 65536 Zeichen.

hwnd

ein Fenster Handle (Pointer)

ptr

ein allgemeiner Pointer (void *)

float

eine Kommazahl einfacher Genauigkeit

double

eine Kommazahl doppelter Genauigkeit

lresult/int_ptr/long_ptr

ein Integer, groß genug um einen Pointer zu speichern, wenn eine x86 oder x64 Versions von AutoIt läuft

lparam/int_ptr/long_ptr

ein Integer, groß genug um einen Pointer zu speichern, wenn eine x86 oder x64 Versions von AutoIt läuft

wparam/uint_ptr/ulong_ptr

ein unsigned Integer, groß genug um einen Pointer zu speichern, wenn eine x86 oder x64 Versions von AutoIt läuft

*

Füge * an das Ende eines anderen Typs um ihn ByRef zu erstellen. Z.B.: "int*" erstellt einen Pointer zu einem "int" Typ.


<top>

Umwandlung von Windows API-Typen zu AutoIt-Typen

WINDOWS API Type

AutoIt Type

LPCSTR/LPSTR

str

LPCWSTR/LPWSTR

wstr

LPVOID

ptr

HWND

hwnd

WPARAM

wparam

LPARAM

lparam

DWORD

dword

LPDWORD

dword*

HANDLE/HINSTANCE

ptr

LONGLONG/LARGE_INTEGER

int64

ULONGLONG/ULARGE_INTEGER

uint64

UINT_PTR

wparam

LONG_PTR

lparam


Eigentlich fehlen noch 2 Typen:
Für ANSI-Zeichen "char" und für UNICODE "wchar". Wir finden diese aber in der Auflistung der Strukturdatentypen.
Beide Typen repräsentieren 1 einzelnes Zeichen. Wie lassen sich damit aber Zeichenketten darstellen? Dazu definiert man ein Array von Zeichen (z.B. char[20]). Wichtig bei der Verwendung von UNICODE: Zeichenketten werden dort NULL-terminiert, d.h. nach den Zeichen des Strings folgt der ASCII-Wert 0 um anzuzeigen, dass hier der String endet. Somit muss die Anzahl von "wchar" um 1 größer sein als die Stringlänge. Wenn in der Funktionsbeschreibung angegeben wird, dass der String nullterminiert ist, gilt das ebenso bei ANSI.
Allerdings erleichtert uns AutoIt die Verwaltung dieser beiden Typen durch Verwendung von "str" und "wstr".
Da erst ab Windows 2000 volle UNICODE Unterstützung existiert, sind fast alle Stringfunktionen mit ANSI- und UNICODE- Version ausgestattet. Sie werden unterschieden durch den Zusatz "A" für ANSI-Funktion und "W" (wide char) für UNICODE-Funktion. Also "StringFunktionA" und "StringFunktionW".

Wenn ihr nicht gerade für Win98 programmiert (ha-ha), macht es eigentlich auch keinen Sinn, die ANSI-Varianten der Funktionen zu verwenden. Mein Tipp: Setzt, wenn möglich immer die UNICODE-Variante ein.

<top>

Strukturen

Wenn wir mit Dll arbeiten wollen, müssen wir auch über Strukturen reden.
Was also ist eine Struktur?
Eine Struktur weist Ähnlichkeit mit einem Array auf. D.h., unter einem Variablennamen werden 1 oder mehrere Werte gespeichert.
Was sind die Unterschiede?
Im Array ist es egal, was für Datentypen die einzelnen Werte haben. Sie können dort auch ständig wechseln. Die Anzahl der Element in einem Array ist niemals fix, mit ReDim läßt sich die Größe zur Laufzeit ändern.
Ganz anders die Struktur. Hier bestimme ich beim Erstellen der Struktur, welche Datentypen die Inhalte haben dürfen. Das ist wichtig, da in den Dll-Funktionen genau festgelegt ist, welche Datentypen an welcher Position erwartet werden. Die Anzahl der Strukturelemente kann zur Laufzeit nicht verändert werden.

AutoIt hat schon eine Vielzahl von Strukturen implementiert. In der Hilfe findet ihr diese unter <User Defined Functions> <StructureConstants Management>.
Als Hauptinformationsquelle verwende ich die Windows Api Referenz. Dort findet ihr neben allen API-Funktionen die zugehörigen Messages, Notifikationen, Strukturen und Konstanten.

Struktur Erstellen

Zum Erstellen einer Struktur stehen in AutoIt folgende Datentypen zur Verfügung

Type

Details

byte

8Bit (1Byte) signed char

ubyte

8Bit (1Byte) unsigned char

char

8Bit (1Byte) ASCII char

wchar

16Bit (2Byte) Wide char

short

16Bit (2Bytes) signed integer

ushort

16Bit (2Bytes) unsigned integer

int

32Bit (4Bytes) signed integer

uint

32Bit (4Bytes) unsigned integer

long

32Bit (4Bytes) signed integer

ulong

32Bit (4Bytes) unsigned integer

dword

32Bit (4Bytes) unsigned integer

ptr

32Bit (4Bytes) integer

hwnd

32Bit (4Bytes) integer

float

32Bit (4Bytes) floating point

double

64Bit (8Bytes) floating point

int64

64Bit (8Bytes) signed integer

uint64

64Bit (8Bytes) unsigned integer

int_ptr

32 oder 64Bit signed integer (äbhängig ob x86 oder x64 Version von AutoIt genutzt wird)

uint_ptr

32 oder 64Bit signed integer (äbhängig ob x86 oder x64 Version von AutoIt genutzt wird)

long_ptr

32 oder 64Bit unsigned integer (äbhängig ob x86 oder x64 Version von AutoIt genutzt wird)

ulong_ptr

32 oder 64Bit unsigned integer (äbhängig ob x86 oder x64 Version von AutoIt genutzt wird)


Eine Struktur erstellen wir mit:

DllStructCreate

In der allgemeinen Definition ist als optionaler Parameter noch Pointer angegeben. Darauf gehe ich im Punkt Pointer näher ein.

Die Struktur wird in einem String definiert. Der String kann neben dem Datentyp auch noch einen Bezeichner führen. Dieser ist aber nicht zwingend erforderlich.
Im folgenden Bsp. enthält die Struktur 4 Integerwerte. Die Definition eines Wertes lautet: Datentyp-Leerzeichen-Bezeichner, die Abgrenzung zwischen den Werten erfolgt durch ein Semikolon.

DllStructCreate_2

Und zum Vergleich die identische Struktur, ohne Bezeichner.

DllStructCreate_3

<top>

In eine Struktur schreiben

Um Werte an eine Struktur zu übergeben, verwenden wir die Funktion

DllStructSetData

Struct ist die Variable, der mit "DllStructCreate" erstellten Struktur
Element ist entweder der 1-basierte Index des Wertes in der Struktur oder der Bezeichner
value ist der zu übergebende Wert
index [optional] ist das Element ein Array, kann auf ein einzelnes Arrayelement mit diesem Index zugegriffen werden

StructSetData2

StructSetData3

 StructSetData4

<top>

Aus einer Struktur lesen

Um Werte aus einer Struktur zu lesen, verwenden wir die Funktion

StructGetData

Struct ist die Variable, der mit "DllStructCreate" erstellten Struktur
Element ist entweder der 1-basierte Index des Wertes in der Struktur oder der Bezeichner
index [optional] ist das Element ein Array, kann auf ein einzelnes Arrayelement mit diesem Index zugegriffen werden

Diese Funktion ist das exakte Gegenstück zu "DllStructSetData".

DllStructGetData2

<top>

Pointer

Bei dem Pointer handelt es sich nicht um die Jagdhundrasse Pointer ( :P Scherz am Rande ), sondern um eine Variable, die auf eine Speicheradresse verweist. Ich hatte ja bereits beim Erstellen der Struktur darauf hingewiesen.
Nehmen wir an, irgendeine WM_Message liefert uns über den Parameter "lParam" Informationen zu einer Desktopkoordinate. Aus der Beschreibung zu der Message wissen wir, dass die Werte in der Datenstruktur "int;int" übergeben werden. Wir müssen also beim Erstellen der Struktur den Pointer mit angeben, sodass die Werte, auf die der Pointer verweist, an die Struktur übergeben werden können.
So könnte das dann aussehen:

Pointer

Im umgekehrten Fall haben wir eine Struktur und die Funktion erwartet einen Pointer dafür. Somit müssen wir einen Pointer erstellen, der auf diese Struktur verweist. Der Pointer kann entweder auf die gesamte Struktur oder auf ein einzelnes Element der Struktur verweisen. Dafür nutzen wir in AutoIt die Funktion

Pointer2

Struct ist die Variable, der mit "DllStructCreate" erstellten Struktur
Element [optional] der 1-basierte Indexwert des Elementes aus der Struktur, auf das der Pointer zeigen soll

Nehmen wir an, wir haben folgende Struktur: "int;int;int;ptr", wobei der Pointer auf eine "int;int"-Struktur verweisen soll. Das sieht dann so aus:

Pointer3

<top>

Dll Call

So sieht ein DllCall in AutoIt allgemein aus:

dllcall

DLL die Dll, die wir nutzen möchten
Rückgabetyp der Datentyp, den der Dll-Aufruf zurückliefert
Funktionsname die Funktion aus der Dll, die wir aufrufen möchten
Parametertyp [optional] Datentyp des Parameters
Parametername [optional] Name des Parameters
... [optional] weitere Parametertypen/ -namen

       
Woher weiß ich, ob mein Aufruf erfolgreich war?
Dazu werte ich die Rückgabe des DllCall aus. Hierbei ist folgendes zu beachten:

Die Funktion DllCall() liefert ein Array zurück. Das Array enthält als erstes Element (Array[0]) den Ergebniswert des DllCall. Dieser ist im Fehlerfall = 0, ansonsten <> 0. Für jeden Parameter des Aufrufs wird ein Arrayelement erstellt. Sofern dieser Parameter vom Aufruf nicht verändert wird, ist also der übergebene Parameterwert auch im Ergebnisarray.
Für die Fehlerabfrage empfiehlt sich also:

DllCall2

<top>

Array (1D) im DllCall

Es kommt vor, dass ein Dll-Aufruf als Element ein Array zur Übergabe/Übernahme von Werten verlangt.
Einen Strukturdatentyp "array" haben wir nicht zur Verfügung. Aber wir wissen, dass Datentypen in Arrayform übergeben werden können: "typ[anzahl]".

Somit bietet sich an, ein Pointerarray zu erstellen, dessen Elemente jeweils auf einen Wert unseres Datenarrays verweisen.
Die Struktur sieht dann folgendermaßen aus:
"Pointer-Array[Anzahl_Arrayelemente];je_Arrayelement_ein_Strukturelement...."

Nehmen wir an, das ist unsere Dll-Definition:

DLL Meine.DLL
Funktion IrgendWas
Returntyp long
Parameter ElementName
Parameter Typ String Array

Die Strings als Zeichenketten im Array

structcreat_array_char.png

Die Struktur ist erstellt und wird nun mit Daten befüllt

struct_arr_char_fill.png

Und so sieht es dann in der Struktur aus:

struct_array_inhalt.png

Jetzt übergeben wir das Array (bzw. den Pointer, der auf das Array verweist) an den DllCall.
In unserer Struktur $arrChar ist das erste Strukturelement das Array mit den Pointern, welche auf die eingetragenen Werte verweisen.

dllcall_array.png

Als 'Nachweis', dass tatsächlich über das Pointerarray  auf die Werte zugegriffen wird, lesen wir die Daten aus, indem wir eine neue Struktur erstellen, die über die verwendeten Pointer befüllt wird.

struct_arr_char_read.png

<top>

Beispiele


Beep( )

Wir werden jetzt die Funktion Beep( ) als DllCall ausführen. Die Funktion verwendet 2 Parameter: Frequenz und Dauer. Wir führen den Aufruf mit 500 Hz über 700 ms aus.
Hier die Originalbeschreibung aus der Windows API-Referenz:

Declare Function Beep Lib "kernel32" Alias "Beep" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
· dwFreq
Specifies the frequency, in hertz, of the sound. This parameter must be in the range 37 through 32,767 (0x25 through 0x7FFF).
· dwDuration
Specifies the duration, in milliseconds, of the sound.

Die Funktion wird hier in folgender Form dargestellt:

"Funktionsname" "DLL" ["Alias-Funktionsname"] ("Parametername", "Parametertyp", ...) Rückgabetyp

Der Dll Aufruf in AutoIt erwartet, wie schon gezeigt, folgende Darstellung:

DllCall

Suchen wir uns die notwendigen Angaben zusammen:

DLL kernel32
Rückgabetyp long
Funktionsname Beep (der Alias braucht nur verwendet werden, wenn er vom Funktionsnamen abweicht)
Parmetertyp long
Parametername dwFreq (500)
Parmetertyp long
Parametername dwDuration (700)

Und so sieht nun der fertige Aufruf aus:

Beep

<top>

MsgBox "Hallo Welt!"

Wir wollen per MsgBox den Text "Hallo Welt!" ausgeben.
Hier wiederum die Infos aus der Windows API-Referenz zur MsgBox:

Declare Function MessageBox Lib "user32" Alias "MessageBoxA"
(ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal wType As Long) As Long
· hWnd
Identifies the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.
· lpText
Points to a null-terminated string containing the message to be displayed.
· lpCaption
Points to a null-terminated string used for the dialog box title. If this parameter is NULL, the default title Error is used.
· uType
Specifies a set of bit flags that determine the contents and behavior of the dialog box.

hWnd hier können wir 0 übergeben, da wir die Nachricht allgemein anzeigen
lpText hier kommt unser Text rein: "Hallo Welt!"
lpCaption das ist der Titel der MsgBox, wir nehmen: "Test"
uType wir brauchen nur den OK-Button, das ist die 0

So sieht es fertig aus:

HalloWeltA

Ihr seht in dieser Funktion den Suffix "A" am Funktionsnamen. Für UNICODE wäre also folgender Aufruf zuständig.

HalloWeltW

<top>

GetVolumeInformation


Nun zu einer Funktion, die uns mehrere Ergebniswerte liefert.
Wieder die Beschreibung aus der API-Referenz:

Declare Function GetVolumeInformation Lib "kernel32" Alias "GetVolumeInformationW"
(ByVal lpRootPathName As String, ByVal lpVolumeNameBuffer As String, ByVal nVolumeNameSize As Long, lpVolumeSerialNumber As Long, lpMaximumComponentLength As Long, lpFileSystemFlags As Long, ByVal lpFileSystemNameBuffer As String, ByVal nFileSystemNameSize As Long) As Long
· lpRootPathName
Points to a string that contains the root directory of the volume to be described. If this parameter is NULL, the root of the current directory is used. If this parameter is a UNC name, you must follow it with an additional backslash. For example, you would specify \\MyServer\MyShare as \\MyServer\MyShare\.
· lpVolumeNameBuffer
Points to a buffer that receives the name of the specified volume.
· nVolumeNameSize
Specifies the length, in characters, of the volume name buffer. This parameter is ignored if the volume name buffer is not supplied.
· lpVolumeSerialNumber
Points to a variable that receives the volume serial number. This parameter can be NULL if the serial number is not required.
· lpMaximumComponentLength
Points to a doubleword value that receives the maximum length, in characters, of a filename component supported by the specified file system. A filename component is that portion of a filename between backslashes. The value stored in variable pointed to by *lpMaximumComponentLength is used to indicate that long names are supported by the specified file system. For example, for a FAT file system supporting long names, the function stores the value 255, rather than the previous 8.3 indicator. Long names can also be supported on systems that use the New Technology file system.
· lpFileSystemFlags
Points to a doubleword that receives flags associated with the specified file system.
· lpFileSystemNameBuffer
Points to a buffer that receives the name of the file system (such as FAT or NTFS).
· nFileSystemNameSize
Specifies the length, in characters, of the file system name buffer. This parameter is ignored if the file system name buffer is not supplied.

Hier gehts nun richtig zur Sache. :=)
In den Parameterbeschreibungen findet ihr fast überall: "Points to ...". Das bedeutet für uns, dass auf einen Pointer verwiesen wird, der zurÜbergabe/Übernahme von Werten benötigt wird.
Wie das umzusetzen ist, haben wir ja schon bei den Themen Struktur und Pointer kennengelernt.

Hier die Vorbereitung der Parameter:

GetVolumeInfo

Das ist dann der zugehörige Aufruf:

GetVolumeInfo2


Und hier die Auswertung der ermittelten Daten:

GetVolumeInfo3

<top>

GetCursorPos( )


Diese Funktion enthält als Parameter bereits eine Struktur.

Declare Function GetCursorPos Lib "user32" Alias "GetCursorPos" (lpPoint As POINTAPI) As Long
· lpPoint
Points to a POINT structure that receives the screen coordinates of the cursor.

Das Vorgehen für uns ist wie im letzten Bsp., die geforderte Struktur erstellen und mit einem Pointer darauf verweisen.
So sieht die enthaltene Struktur aus:

Type POINTAPI
  x As Long
  y As Long
End Type

Das übersetzen wir jetzt nach AutoIt:

GetCursorPos

<top>