Windows-Schnittstellen

Aus Wiki des Deutschsprachige Xbaseentwickler e. V.
Zur Navigation springen Zur Suche springen


Grundsätzliches

Durch die Möglichkeit, aus Xbase-Programmen heraus auf Windows APIs zuzugreifen, erweitern sich die Möglichkeiten des Programmierers.

Die Windows APIs sind grundsätzlich in DLLs hinterlegt, die mit den DLL-Funktionen von Xbase++ angesprochen werden können.

Problematisch an dieser Stelle ist jedoch, dass die Schnittmenge zwischen Xbase++ und den Windows APIs nur relativ einfache Feldtypen umfasst. Wenn komplexere Strukturen gefordert werden, muss der Programmierer entweder auf die von Alaska Software bereitgestellte BAP Bibliothek zurückgreifen, oder ein 3rd Party Produkt wie OT4XB.


Zugriffsmöglichkeiten

"externer" Zugriff über Windows-DLLs

Bestimmte Windows-DLLs erlauben einen direkten Zugriff, z.B. zum Öffnen von bestimmten Dokumenten oder Internet-Adressen.

Hier wird ein entsprechender Aufruf über RunShell() abgesetzt, das weitere erledigt Windows.

Da dieser Zugriff quasi von "aussen" erfolgt, wird er hier als "externer" Zugriff bezeichnet.

Entscheidend ist hier auch, dass in den meisten Fällen das Ergebnis des Aufrufs nicht im Programm verarbeitet werden muss.


"interner" Zugriff über Windows- und Fremdanbieter-DLLs

Beim "internen" Zugriff wird auf ein API (oder mehrere APIs) der DLL zugegriffen. Normalerweise gibt die DLL Informationen zurück, die im Programm weiterverwendet werden.

Hierfür muss zuerst eine Verbindung zwischen dem laufenden Programm und der DLL hergestellt werden. Dieser Schritt ist einem fOpen() vergleichbar: das Programm stellt über Windows eine Verbindung mit der DLL her und kann danach in der DLL hinterlegte APIs ausführen.


Suchen nach DLLs

DLLs werden standardmässig in den Verzeichnissen gesucht, die im Windows-Pfad angegeben sind, sowie im aktuellen Verzeichnis. Befindet sich die DLL nicht in einem dieser Verzeichnisse, kann das Verzeichnis über das API SetDllDirectory (definiert in der Kernel32.DLL) mit in die Liste aufgenommen werden.

Ein Beispiel für die Verwendung von SetDllDirectory findet sich weiter unten.


Suche nach dem richtigen API

Jede DLL bringt ihre eigenen APIs mit, und man ist im Normalfall auf die Dokumentation des Anbieters angewiesen, um die verfügbaren APIs und ihre Parameter und Rückgabewerte zu ermitteln.

Gerade für die von Microsoft bereitgestellten APIs gibt es diese Dokumentation so nicht, man ist also oft auf Versuch und Irtum oder die Kataloge der Suchmaschinen angewiesen.


Programmierung

Der Zugriff auf DLLs benötigt - analog zum Low-Level Zugriff auf Dateien - eine Verbindung zwischen dem Xbase-Programm und der betroffenen DLL. Diese Verbindung wird durch einen DLL-Handle abgebildet.

Es gibt zwei Wege für den Zugriff. Der erste Weg führt über die Funktion DllCall() und beinhaltet das implizite Öffnen der DLL, die Ausführung des API und das Schliessen der DLL. Wenn nur ein Zugriff benötigt wird, ist dies von der Programmierung der am wenigsten aufwändige Weg.

Die Alternative ist ein Öffnen der DllLoad(), Ausführen des/der API/s mittels DllCall() und ein abschliessendes Schliessen mittels DllUnLoad().

Da bei DllCall() standarmässig Xbase-orientierte Calling Conventions verwendet werden, sollte die Header-Datei "Dll.ch" eingebunden werden.


Anwendungsbeispiele

Öffnen einer URL im Standard-Browser

Um eine URL über den Standard-Browser zu öffnen, kann der folgende Programm-Code verwendet werden:

cProgram := "RunDLL32.exe"
cLine    := "url.dll,FileProtocolHandler " + cURL  // im Format http(s)://www.domain.org/
RunShell(cLine, cProgram, .T., .T.)

In diesem Beispiel bedienen wir uns indirekt der URL.dll, indem RunDLL32.exe aufgerufen wird, mit der Anforderung, aus der URL.dll das API FileProtocolHandler auszuführen und die vom Programm vorgegebene URL zu öffnen.


Öffnen einer Datei mit dem Standard-Programm

Programme können bei der Installation hinterlegen, für welche Dateitypen (definiert durch die Namenserweiterung) sie sich "verantwortlich" fühlen.

Die API ShellExecuteA aus Shell32.DLL dient dazu, eine bestimmte Datei mit dem Standardprogramm zu önnen.

lErfolg := DllCall("Shell32.DLL", DLL_STDCALL, "ShellExecuteA",;
            AppDesktop():GetHWND(), "open", cPath + cFile,;
            NIL, CurDir(), SW_NORMAL )

(Die Parameter des API ShellExecuteA sind der entsprechenden Microsoft-Dokumentation zu entnehmen.)


Integration einer API als normaler Funktionsaufruf in einem Xbase++-Programm

Xbase++ bietet die Möglichkeit, API Aufruf wie einen normalen Funktionsaufruf in ein Programm zu integrieren:

#include "dll.ch"

DLLFUNCTION SetDllDirectoryA(cPathName) USING STDCALL FROM KERNEL32.DLL

DLLFUNCTION ist in der DLL.ch definiert, daher muss diese Include-Datei im Programm referenziert werden. Ein Blick in die Include-Datei zeigt, dass eine FUNCTION mit dem Namen SetDllDirectoryA definiert wird, die einen Parameter (den Namen des Verzeichnisses) entgegennimmt. Die DLL Kernel32 wird mittels DllLoad() geöffnet, das API mittels DllCall() ausgeführt und die Dll danach mittels DllUnLoad() wieder geschlossen.

Wichtig ist, dass die programmintern definierte Funktion exakt den gleichen Namen haben muss wie das API, auf das zugegriffen werden soll.

Wird ein nicht-existentes API referenziert, erzeugt die Laufzeitumgebung einen Fehler, der in die falsche Richtung deutet:

Xbase++ version     : Xbase++ (R) Version 1.90.355
Operating system    : Windows 7 06.01 Build 07601 Service Pack 1
----------------------------------------------------------------
oError:args         :
          -> VALTYPE: N VALUE: 1993277440
          -> VALTYPE: N VALUE: 32
          -> VALTYPE: C VALUE: SetDllDirectory
          -> VALTYPE: C VALUE: C:\Temp
oError:canDefault   : Y
oError:canRetry     : N
oError:canSubstitute: N
oError:cargo        : NIL
oError:description  : Function is not declared
oError:filename     :
oError:genCode      :         21
oError:operation    : dllPrepareCall
oError:osCode       :          0
oError:severity     :          2
oError:subCode      :       2002
oError:subSystem    : BASE
oError:thread       :          1
oError:tries        :          0

Der Fehler liegt aber nicht darin, dass DllPrepareCall() nicht implementiert ist, sondern dass die Funktion, die innerhalb von DllPrepareCall() aufgerufen werden soll, nicht implementiert ist.

Betrachten wir an dieser Stelle noch kurz, was der Preprozessor aus der Anweisung DLLFunction macht:

FUNCTION SetDllDirectoryA( cPathName)
  LOCAL nDll:=DllLoad("KERNEL32.DLL")
  LOCAL xRet:=DllCall(nDll,32,"SetDllDirectoryA", cPathName)
  DllUnLoad(nDll)
RETURN xRet

Zum Zweck der Verdeutlichung habe ich den Output in der PPO-Datei über mehrere Zeilen verteilt.


(Artikelinarbeit)