% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[utf8]{inputenc} \usepackage{multicol,babel} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} \renewcommand{\reftextbefore}{auf der vorherigen Seite} \renewcommand{\reftextfacebefore}{auf der gegenüberliegenden Seite} \renewcommand{\reftextafter}{auf der nächsten Seite} \renewcommand{\reftextfaceafter}{auf der gegenüberliegenden Seite} \begin{document} % \renewcommand{\figurename}{Tabelle} \title{Win32Forth — Wie ruft man damit Routinen \\\hspace*{\fill}von Windows auf?} \ifx\shorttitle\undefined\else \shorttitle{Win32Forth ruft Windows} \fi \author{Alex Mcdonald (Ins Deutsche übertragen von Michael Kalus)} \maketitle Es macht Mühe, sich in all die Routinen einzufuchsen, die Windows bietet. Aber es ist auch kein Buch mit sieben Siegeln. Wer sich also da herantasten will, tut gut daran, ein Werkzeug zu nehmen, das erste Erfolgserlebnisse sofort ermöglicht. Und einen dann auf dem Weg zur vollen Kontrolle über die CALLs auch nicht im Stich lässt. Mein Eindruck ist, dass Win32Forth (W32F) gut vermittelt, wie das Betriebssystem Windows für eigene Programme genutzt werden kann. \begin{multicols}{2} Dieses Forth von Tom Zimmer, das er der Öffentlichkeit schenkte und bis 2002 noch selbst betreut hat, wird seither von einer aktiven Gruppe von Benutzern weitergepflegt$^1$. So ist das W32F inzwischen zu einem erstaunlich umfangreichen System geworden. Neben der klassischen Forth--Konsole bietet es eine komfortable integrierte Entwicklungsumgebung (IDE) an, deren wichtigste Features sind: Syntax highlighting, Projektmanagement, Dialog Editor, Debugger. Und mit der ForthForm lassen sich inzwischen Ein-- und Ausgabe--Fenster einfach zusammenklicken. Dabei ist ForthForm inzwischen ebenso Bestandteil der IDE geworden wie der Projekt--Manager. Die Standalone--Versionen der beiden Programme werden von der Gruppe nicht mehr weitergepflegt. Dies gilt auch für Toms Editor WinView (bzw. WinEd). Um Berührungsängste abzubauen und einen Anfang zu finden, ist es vielleicht hilfreich, sich einmal das Grundlegende zu den DLLs aus der umfangreichen Dokumentation des W32F$^2$ heraus zu picken. Drum habe ich es ins Deutsche übersetzt. Die Entwicklergruppe war damit einverstanden, doch bat Dirk Busch um den Hinweis, dass einiges in der Dokumentation inzwischen von der aktuellen Entwicklung des W32F überholt worden ist. Fragen dazu werden gern aus der Gruppe beantwortet. Hingewiesen sei auch darauf, dass derzeit eine Betaversion der nächsten W32F Version 6.13 unter: \url{http://www.schneider-busch.de/dirk/forth/download/w32f61301.exe} zum Download bereitsteht. Ab dieser Version wird es ein nützliches Feature in Bezug auf DLLs geben. Im Menü \emph{Tools} der Konsole findet sich der Eintrag \emph{List DLL exports}, mit dem man sich die Namen der Funktionen, die von einer DLL exportiert werden, anzeigen lassen kann. (mka) \section{Grundlegendes} Es ist einfach, mit Win32Forth Windows--Aufrufe zu machen --- wenn man weiß, wie. Dieses Tutorial erklärt, welche Schlüsselworte für die Verbindung zu den DLLs benutzt werden, und gibt einige einfache Beispiele, um das zu veranschaulichen. Dabei wird angenommen, dass du eine Version des Win32Forth benutzt, die CALL-- und PROC--Instruktionen bereits im Forthkern hat. Tippe .LIBS, um das zu prüfen. Die Bedeutung der Spalten wird weiter unten im Text noch erklärt werden; die Adressen und die Liste selbst mag auf deiner Maschine anders sein, das variiert je nach Betriebssystem und Win32Forth--Version. \begin{verbatim} .libs Location Handle Type Name -------- -------- ---- ----------- 000450D8 71710000 APP COMCTL32.DLL 0002C91C 76B30000 APP COMDLG32.DLL 0001794C 10000000 APP WINCON.DLL 00017934 782F0000 APP SHELL32.DLL 0001791C 7C2D0000 APP ADVAPI32.DLL 00017908 77F40000 APP GDI32.DLL 00013490 00D20000 APP PRINT.DLL 0000E6D8 63180000 APP SHLWAPI.DLL 0000BFA0 7C570000 KERN KERNEL32.DLL 0000B0FC 77E10000 APP USER32.DLL 0000A970 00D40000 APP CONSOLE.DLL \end{verbatim} \section{Grundlagen der DLLs} DLLs (Dynamic Link Libraries) sind auch nur Programme auf deinem PC wie alle anderen, haben jedoch einige wenige grundlegende Unterschiede. DLLs haben gewöhnlich kein sichtbares Interface, also kein \emph{Fenster}. Und zudem können sich ganz verschiedene Prozesse eine DLL teilen. So liegt gewöhnlich nur eine Kopie davon im Speicher, ganz egal, wie viele Programme die dann benutzen. DLLs stellen vorgefertigte Funktionen zur Verfügung, die dann von allen anderen Programmen benutzt werden können. Und um sie effektiv zu verwenden, braucht man deren Dokumentation. \section{Win32Forth--Worte dafür} Es braucht nur wenige Forthworte, um einen Aufruf externer DLLs zu machen: \begin{itemize}\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt} \item Debugging words: \texttt{.PROCS}, \texttt{.LIBS} \item Defining words: \texttt{PROC}, \texttt{WINLIBRARY} \item Calling words: \texttt{CALL}, \texttt{REL>ABS}, \texttt{ABS>REL} \item Naming words: \texttt{AS} \end{itemize} \section{Schritt 1. Finde die Dokumentation der DLL} Ohne solch ein Dokument ist es weder möglich, herauszufinden welche Namen die Funktionen einer DLL haben, noch welche Parameter übermittelt werden müssen, noch was zurückkommt. Wertvolle Dokumentationen zu den grundlegenden DLLs findet man bei msdn.microsoft.com, aber es gibt auch eine Menge weiterer Anbieter dafür. Für speziellere DLLs braucht man die von denen mitgelieferten Dokumente. \section{Schritt 2. Melde die DLL bei Win32Forth an} Wird dem Win32Forth der Name einer DLL mitgeteilt, lädt es diese. Die Anweisung WINLIBRARY KERNEL32.DLL ermöglicht es dem W32F, diese DLL zu laden, damit dann deren Routinen benutzt werden können. Der Suchpfad für die DLLs variiert je nach Betriebssystem (95/98/ME/NT/2000/XP). Wird einfach nur der DLL-Name angegeben, muss die DLL im Systemverzeichnis liegen, oder in dem Verzeichnis, von dem aus W32F selbst aufgerufen worden ist. Die meisten DLLs werden auf diese Weise dann auch gefunden, und es wäre ungewöhnlich, wenn Pfadangaben gemacht werden müssten. WINLIBRARY kann auch mehrfach aufgerufen werden, es wird trotzdem immer nur eine einzige Kopie davon im Speicher sein. Mit .LIBS erfährt man, was schon alles geladen wurde: \begin{verbatim} Location Handle Type Name -------- -------- ---- ----------- 0003EA5C -noload- APP TESTIT.DLL 00014C08 10000000 APP WINCON.DLL . . . \end{verbatim} Dieses Wort wird gewöhnlich nur von der Kommandozeile einer Konsole aus benutzt. Im Beispiel sieht man, dass TESTIT.DLL zwar spezifiziert, aber noch nicht geladen wurde. Falls du Fehlermeldungen beim Aufruf von Routinen hast, die W32F eigentlich schon kennen sollte, schau bei der Adresse des \emph{handle} nach. Wird dort noch \verb|-noload-| angegeben, konnte W32F die Bibliothek für diese Routine nicht finden. \section{Schritt 3. Melde deren Prozeduren$^3$\\\hspace*{\fill}bei Win32Forth an} Das Forthwort \texttt{PROC} wird verwendet, um die verschiedenen Prozeduren einer DLL (functions) vom W32F aus zu nutzen. In den Dokumenten der DLLs finden sich beispielsweise solche Angaben: \subsection{AllocConsole} Die Funktion \texttt{AllocConsole} startet eine neue Konsole für Programmaufrufe. (to allocate = Platz zuweisen)\\ \texttt{BOOL AllocConsole(void);} \subsection{Parameter} Die Funktion nimmt keine Parameter an. \subsection{Rückgabewerte} Wenn die Funktion erfolgreich beendet wurde, ist der zurückgegebene Wert verschieden von null (nonzero). Sollte die Funktion hingegen scheitern, ist der Wert null. Diese Funktion legt man im W32F nun so an: \texttt{0 PROC AllocConsole} d.$\,$h., die Funktion nimmt 0 Parameter und heißt \verb|AllocConsole|. Beachte dabei unbedingt die Groß-- und Kleinschreibweise! Du MUSST die Namen so wie in deren Dokumention gezeigt eintippen. Obschon gerade in diesem Beispiel die Zahl nur symbolischen Wert hat und lediglich für die Dokumentation gedacht ist, denn ihr Wert kann 0 bis 127 sein, darf sie doch nicht negativ sein, weil damit spezielle Prozeduren des Systems gekennzeichnet werden. (Und wie schließt man die Konsole wieder? Lies aufmerksam weiter und du wirst es herausfinden.) Dieser Schritt ist eigentlich optional (und aus didaktischen Gründen hier angeführt) --- man muss die PROC--Anweisungen eigentlich nicht ausdrücklich angeben, weil W32F während der CALL--Anweisung alle Prozeduren dynamisch selbst erzeugt. Die Anzahl der Prozeduren ist so aber auf die maximal dynamisch erzeugbaren begrenzt, das sind derzeit so um die 100 bis 150 Stück. Mit .PROCS sieht man welche Prozeduren schon erzeugt worden sind, und man kann die Ausgabe auch eingrenzen auf einen Namensteil (hierbei ist die Groß-- oder Kleinschreibung mal ohne Bedeutung): \begin{footnotesize} \begin{verbatim} .PROCS ALLOC Location ProcName Prm ProcEP LibName -------- -------- --- -------- -------- 0003EA5C AllocConsole 0 00012814 GlobalLock 77E977E4 KERNEL32.DLL 000127F4 GlobalAlloc 77E96646 KERNEL32.DLL 0000B830 HeapReAlloc 4 0000B7F8 HeapAlloc 3 77FCB10F KERNEL32.DLL Allocated Space: 12,288 bytes Free Space Left: 6,997 bytes Displayed 5 of 184 procedures defined \end{verbatim} \end{footnotesize} \section{Schritt 4. Funktion aufrufen} Da nun die DLL (library) angelegt ist, und optional auch deren Prozedur, können wir mit CALL AllocConsole ihre Funktionen ausführen --- nochmal: Die Schreibweise ist von Bedeutung und muss der Doku genau entsprechen! Nach diesem CALL sollte man eine frisch geöffnete DOS--Konsole sehen und eine 0 auf dem Stack vorfinden. \section{Weitere Eigenschaften von DLLs} \subsection{Die Abfolge der Parameter} Forth übermittelt alle Parameter an die Windows--Funktionen mit dem Datenstack. Dort liegen sie in umgekehrter Abfolge. Also, wenn die Dokumentation fordert, dass \texttt{BOOL Function ( A, B, C )} anzugeben ist, schreiben wir in Forth \texttt{C B A CALL Function}. Diese Umkehrung ist grundsätzlich nötig, um die Forth--Aufrufe mittels \texttt{CALL} laufen zu lassen. \subsection{Prozeduren benennen} Manche Funktionen im Windows haben bis zu drei Namen. Zum Beispiel gibt es da \texttt{CharUpperBuff}, \texttt{CharUpperBuffA} und \texttt{CharUpperBuffW} nebeneinander. Tatsächlich aber sind es nur zwei verschiedene Routinen. Schau dir die folgende Liste an, die mit \texttt{.PROCS} erstellt wurde$^4$: \begin{footnotesize} \begin{verbatim} Location ProcName Prm ProcEP LibName -------- -------- --- -------- -------- 0003EA98 CharUpperBuffW 2 77E1236F USER32.DLL 0003EA74 CharUpperBuffA 2 77E1263D USER32.DLL 00003350 CharUpperBuff 2 77E1263D USER32.DLL \end{verbatim} \end{footnotesize} In der Spalte \texttt{ProcEP} steht die Startadresse (entrypoint) einer Prozedur. Wie man sieht, rufen \texttt{CharUpperBuff} und \texttt{CharUpperBuffA} dieselbe Funktion auf, nämlich die für ASCII--Zeichen genutzte, bei der jedes Zeichen durch 8 Bit dargestellt wird, also eine 1--Byte--Funktion. W hingegen ist die Funktion für den UNICODE, also die 2--Bytes--Funktion. Gewöhnlich wirst du die Ascii--Funktion benutzen, da W32F ein 1--Byte--pro--Zeichen--System ist. Von jeder Funktion, die sowohl in der A-- wie in der W--Variante vorkommt, lädt W32F automatisch nur die A--Version, ohne dass du das extra aussuchen musst. Falls du aber wirklich die W--Funktion haben willst, musst du sie ausdrücklich aufrufen. Also z.B. so: \texttt{2 PROC CharUpperBuffW} \subsection{Die zurückgegebenen Werte (return values)} Generell lässt jede Funktion immer nur einen Wert auf dem Stack zurück. Weil die meisten Funktionen in C oder C++ geschrieben sind, und daher multiple Werte gar nicht zurückgeben können, muss man andere Techniken benutzen, soll eine Funktion mehrere Werte zurückgeben. \texttt{BOOL} ist ein Beispiel für solch einen Rückgabewert. Dabei steht im Windows die 0 für \emph{wahr} (oder OK) und die 1 für \emph{falsch} (nicht--OK). Das ist anders herum als im Forth. Nimm \texttt{0=}, um \texttt{BOOL} auszuwerten und zum forthigen \texttt{TRUE} oder \texttt{FALSE} zu kommen: \verb|CALL AllocConsole 0= IF ( alles ist OK ..)| Oder du wertest das so aus: \verb|CALL FreeConsole ABORT" FreeConsole failed"| \texttt{INT}, \texttt{HANDLE}, usw. ergeben 32--Bit--Werte (unsigned or signed 32 bit) auf dem Stack. Verwende sie wie sonst im Forth auch. Auch \texttt{VOID} gibt einen 32--Bit--Wert auf den Stack, der hat aber keine Bedeutung (void = leer, Lücke) und kann einfach mittels \texttt{DROP} verworfen werden. \subsection{Parameter von Funktionen interpretieren} Leider kommen die Parameter von Funktionen in unterschiedlichen Geschmacksrichtungen daher. So muss neben deren Abfolge auch deren Datentyp stimmen, damit der \texttt{CALL} funktioniert. Ein Beispiel: \begin{footnotesize} \begin{verbatim} BOOL ReadFile( HANDLE hFile, // handle to file LPVOID lpBuffer, // data buffer DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // number of bytes read LPOVERLAPPED lpOverlapped // overlapped buffer ); \end{verbatim} \end{footnotesize} wird im W32F zu: \begin{footnotesize} \begin{verbatim} 0 VALUE HANDLE CREATE BUFFER 255 ALLOT 20 VALUE BYTESTOREAD VARIABLE BYTESREAD : MYFUNC 0 \ null address for overlapped buffer BYTESREAD \ bytes read BYTESTOREAD \ bytes to read BUFFER \ address of buffer HANDLE \ value of handle CALL ReadFile ; \end{verbatim} \end{footnotesize} Die Parameter--Typen \texttt{HANDLE}, \texttt{BYTE}, \texttt{WORD}, \texttt{DWORD} liefern einfach einen Stackwert. Ihre Namen werden also interpretiert. Falls der Wert in \texttt{HANDLE} 13 wäre, käme die 13 auf den Stack. Die \texttt{LP}xxx--Typen (ohne \texttt{STR}) hingegen sind Zeiger auf Datenbereiche. Es klappt also für gewöhnlich nicht, wenn man \texttt{VALUE} benutzt, um \texttt{LP}xxx--Parameter zu definieren. Besonders wenn die Funktion einen Wert zurückliefert geht das schief, wie im Fall von \texttt{BYTESREAD}. Benutze lieber \texttt{VARIABLE}, weil dann die Adresse auf den Stack kommt. Und die \texttt{LP}x\texttt{STR}--Typen behandeln Zeichenketten, die mit einer Null enden (Null (0) terminated strings). Man benutzt sie z.~B.\ so: \begin{verbatim} : UPPERCASE ( caddr -- caddr ) DUP COUNT SWAP call CharUpperBuff DROP ; \end{verbatim} Es wird also die einfache Adresse einer Zeichenkette mit vorangehendem Zähler (counted string) in die Startadresse der Zeichen und die Länge der Kette umgewandelt, und deren Abfolge wird dann umgekehrt (len addr). Das ist nötig, weil der Windows--Funktionsaufruf zuerst den Zeiger auf die Zeichenkette und dann erst deren Länge erwartet. Zurück kommt dann wieder ein Wert, in diesem Fall ein n, an dem wir aber nicht weiter interessiert sind. Im Übrigen ist dieses Beispiel nicht einmal ein richtiger null--begrenzter Zeichenketten--Aufruf. Aber das hier ist einer: \verb|int lstrlen(LPCTSTR lpString);| Die Funktion zählt die Anzahl der Zeichen in der Kette. So ergibt \verb|Z" ABCDEFG" CALL lstrlen| eine 7 auf dem Stack, das ist die Länge der Zeichenkette. Folgende Zeichenketten--Typen des W32F sind null--begrenzt und sicher verwendbar auch in solchen Windows--Aufrufen: \verb|C"| --- aber nur wenn der Zählwert berücksichtigt wird, geht es gut. Also immer so verwenden: \verb|C" ABCDEFG" COUNT DROP| Nun steht die Adresse auf A, also auf dem Anfang der Zeichenkette, und nicht mehr auf dem Zähler. \verb|S" ABCDEFG"| legt (addr len) auf dem Stack ab. Hierbei zeigt die Adresse bereits richtig auf das A am Anfang der null--begrenzten Kette. Ob len verwendet wird, hängt ab von der Funktion, die folgen soll. \verb|Z" ABCDEFG"| funktioniert wie \verb|C"|, aber mit dem Unterschied, dass gar keine Längenangabe erfolgt. Daher können \verb|Z"|--Ketten direkt benutzt werden für die Windows--Aufrufe. Und hier noch eine Technik für den Fall, dass du deinen eigenen Puffer für eine null--begrenzte Zeichenkette erzeugen willst. \begin{verbatim} CREATE MYSTR 256 ALLOT \ my string S" ABCDEGF" MYSTR PLACE \ move string to MYSTR MYSTR +NULL \ add null on the end MYSTR CALL lstrlen \ use in call. \end{verbatim} \subsection{AS verwenden} Gelegentlich ist es hilfreich, ein Forth--Wort zu haben, mit dem das XT einer Prozedur verwendet werden kann. Auch sind manche DLL--Namen recht lang und obskur ausgefallen, und eine bedeutsamere kürzere Benennung wäre wünschenswert. So fügt \verb|1 PROC ExitThread AS EXIT-TASK| das Wort \texttt{EXIT-TASK} an die aktuelle Wortliste an (current wordlist). AS muss nach der Deklaration einer Prozedur stehen, Kommentare dürfen dazwischen sein. \subsection{Gefährliche Tricks} Nun folgt noch ein gefährlicher Trick. Nutze so etwas nur, wenn du DLL--Funktionen schon sicher aufrufen kannst. Nimm an, dass die Funktion \texttt{BOOL function(LPWORD param);} eine 10 auf die Adresse schreibt, die ihr übergeben worden ist. Zusätzlich gibt sie eine 1 zurück. Dann kann auch so etwas gemacht werden: \verb|0 SP@ ... CALL function| Der Teil vor dem CALL manipuliert den Stack:\\ \verb!0 | addr of previous cell |! Nach dem Funktionsaufruf hat der Stack dann die Werte\\ \verb!10 | 1! Macht man es so, ist keine \texttt{VARIABLE} mehr nötig, um die 10 zu übergeben! Der ReadFile--Aufruf, den wir weiter oben schon mal ausprobiert hatten, kann dann auch so geschrieben werden: \begin{footnotesize} \begin{verbatim} : MYFUNC 0 \ null address for overlapped buffer 0 SP@ \ bytes read = zukünftige 'Variable' BYTESTOREAD \ bytes to read BUFFER \ address of buffer HANDLE \ value of handle CALL ReadFile ; \end{verbatim} \end{footnotesize} Die Funktion liefert auf diese Weise dann 2 Werte direkt auf dem Stack ab, einmal die Anzahl Bytes, die gelesen wurden, und dann oben drauf noch den Rückgabewert. Sie hat also die von \texttt{sp@} ermittelte Adresse wie eine Variable benutzt, um darin die 10 zu übergeben. \section{Anmerkung} Die Dokumentation so eines umfangreichen Systems wie das Win32Forth ist keine leichte Aufgabe, und die neuesten Änderungen am System werden dort nicht immer sofort eingearbeitet sein. Weil es eine Gruppenarbeit ist, war nicht immer so ganz klar, welchem Autor jeweils der Dank gebührt für einen bestimmten Beitrag zu der Dokumentation. Manches war auch von vornherein eine Gemeinschaftsarbeit. Namentlich genannte Mitarbeiter, denen ich an dieser Stelle meinen Dank und meine Anerkennung für ihre mühevolle Arbeit aussprechen möchte, sind (in alphabetischer Reihenfolge): Robert Dudley Ackerman, Ezra Boyce, Dirk Busch, Thomas Dixon, Brad Eckert, Bruno Gauthier, George Hubert, Alex McDonald, Rod Oakford, Andrew Stephenson, Jos van de Ven und noch andere. Dirk Busch danke ich sowohl für die freundliche Hilfe bei der Durchsicht der Übersetzung als auch für die Hinweise auf die neuen Features des W32F 6.13. \section{Fußnoten} (1) Win32Forth \emph{We}\/--Benutzergruppe:\\ \url{http://tech.groups.yahoo.com/group/win32forth/} (2) Grundlagen der DLLs:\\ http://win32forth.sourceforge.net/doc/p-windlls.htm (3) Im englischen Original werden die Begriffe procedure, function und routine synonym benutzt. In der Übersetzung werden daher ebenfalls Prozedur, Funktion oder Routine als Begriffe für dieselbe Sache benutzt. (4) Die Liste \texttt{CharUpperBuff} usw.\ hat erst dann Einträge in den Spalten \texttt{ProcEP} und \texttt{LibName}, wenn die Funktionen auch einmal gelaufen sind. Bis dahin bleiben diese Spalten leer. Um das Beispiel nachvollziehen zu können, überlege, wie du einen Aufruf der Funktionen machen könntest. \end{multicols} \section{Quellen} \begin{enumerate}\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt} \item \emph{Win32Forth, Calling Windows Procedures, The Basics.},\\ Document \texttt{\$ Id: p-windlls.htm,v 1.1 2004/12/21 00:18:57 alex\_mcdonald Exp \$}\\ auf: \url{http://win32forth.sourceforge.net/doc/p-index.htm} \end{enumerate} \end{document}