Dynamic Link Libraries, kurz DLLs, sind Programmodule, die einen Vorteil besitzen. Da sie (in der Regel) aus purem Maschinencode bestehen, werden die in ihnen enthaltenen Funktionen sehr schnell ausgeführt. Leider besitzen sie für Visual Basic-Programmierer einen kleinen Nachteil: Sie können nicht in Visual Basic erstellt werden. Dennoch sind DLLs bei der Visual Basic-Programmierung allgegenwärtig, denn auch bei den OCX-Zusatzsteuerelementen handelt es sich um DLLs, die dank einer besonderen (COM-) Schnittstelle, die eine "normale" DLL nicht besitzt, über Eigenschaften, Methoden und Ereignisse verfügen. In diesem Tutorial wird gezeigt, wie sich DLLs mit Hilfe von Visual C++ programmieren lassen. Auch wenn es in diesem Tutorial um C++-Programmierung geht, werden keine Vorkenntnisse in dieser Sprache vorausgesetzt, da es in erster Linie um das Prinzip der Umsetzung geht und die vorgestellten Beispiele so einfach sind, daß man durch sie gleichzeitig einen ersten Einstieg in C/C++ erhält.
Sie lernen in diesem Mini-Tutorial etwas über:
Am Ende des Tutorials haben Sie das Prinzip der DLL verstanden und sind in der Lage, mit Hilfe eines C/C++-Compilers kleinere DLLs (die erforderlichen C-Kenntnisse vorausgesetzt) umzusetzen und in Visual Basic einzubinden.
Warum überhaupt DLLs?
Warum soll man sich das Leben unnötig kompliziert machen und eine DLL in C, C++ oder Delphi programmieren, wenn die Programmierung mit Visual Basic manches Mal schon kompliziert genug ist? Auf diese Frage gibt es zwei Antworten:
Beides sind Gründe, die nicht in bei jedem Programm im Vordergrund sthen. Dies gilt auch für den zweiten Grund, denn die Auslagerung von Programme in eine DLL führt nicht automatisch zu einem Geschwindigkeitsgewinn. Zwar bietet eine DLL den Vorteil, daß sie aus hochoptimierten Maschinencode besteht, doch kommt es darauf an, DLL-Funktionen an den richtigen Stellen eines Visual Basic-Programms einzusetzen. Bevor man sich entscheidet, ob man eine Programmkomponente durch eine DLL ersetzt, sollte man, z.B. mit Hilfe des Source Code-Profilers der Enterprise-Edition zum Messen der Ausführungsgeschwindigkeit, jene Programmteile identifizieren, in denen das Programm den meisten Teil seiner Zeit verbringt. Nach der klassischen "90/10-Regel" verbringt ein Programm 90% seiner Zeit damit, 10% des Programmcodes auszuführen. Die Optimierung muß daher in diesen 10% ansetzen, sonst hat sie keine oder nur eine geringe Wirkung.
Was ist eine DLL?
Eine
Dynamic Link Library (DLL) ist ein Modul, das eine von Funktionen (und Prozeduren) enthält, die von jedem Windows-Programm auf aufgerufen werden können. DLLs sind Bibliotheken, die anderen Windows-Applikationen Funktionen und Ressourcen zur Durchführung ihrer Aufgabe zur Verfügung stellen. Sie sind wie Windows-Programme ausführbare Module, können jedoch nicht alleine ausgeführt werden, sondern werden stets von einem Task aufgerufen. Eine DLL wird erst dann von Windows in den Speicher geladen, wenn ein Task eine ihrer Funktionen aufrufen möchte. Aus diesem Verhalten von DLLs leitet sich auch ihr Name ab. Das dynamische Linken bedeutet nichts anderes, als daß DLLs erst dann in den Arbeitsspeicher geladen werden, wenn sie benötigt werden. Erst dann, wird die Verbindung zwischem dem Funktionsaufruf und dem Funktionscode hergestellt. DLLs besitzen in der Regel die Dateierweiterung .DLL oder .Exe, unter Windows sind aber auch DLLs mit Dateierweiterungen .Drv, .Fon oder .ocx anzutreffen.Das dynamische Einbinden des Funktionscodes besitzt zwei wichtige Vorteile:
In der Natur von Visual Basic liegt es, daß zum ausführen einer Applikation stets eine DLL benötigt wird. Sie trägt die schöne Bezeichnung
Msvbvm60.dll. Diese DLL stellt den von Visual Basic kompilierten Programmen zur Laufzeit die benötigte Visual Basic-Umgebung zur Verfügung. Wird ein kompliertes Visual Basic-Programm weitergegeben, muß stets die Datei Msvbvm60.dll mitgeliefert werden.Ein weiterer Vorteil bei der Nutzung von DLLs liegt natürlich in der Software-Pflege. Wenn Sie mehrere Programme entwickelt haben, die die gleichen DLLs nutzen und Sie in einer DLL-Funktion Änderungen vornehmen müssen, hat man lediglich diese DLL zu ändern. Das diese Funktion verwendende Programm bleibt dabei so wie es ist.
Wie werden DLL-Funktionen aufgerufen?
Diese Frage wurde bereits in Kapitel 15 des Visual Basic-Kompendiums beantwortet, denn eine DLL-Funktion wird auf die gleiche Weise aufgerufen wie eine API-Funktion. Kein Wunder, denn alle API-Funktionen befinden sich innerhalb von DLLs und sind damit nichts anderes als DLL-Funktionen. Eine "selbst programmierte" DLL bietet aber den Vorteil, daß Sie das Prinzip der Parameterübergabe selber festlegen können und z.B. Datentypen übergeben können, für die es in C/C++ keine Entsprechung gibt.
Der allgemeine Aufbau einer DLL
Jede DLL besitzt einen allgemeinen Aufbau, der auch als Grundgerüst bezeichnet wird. Es sei vorangestellt, daß moderne Compiler diesen Rahmen bereits vorgegeben, so daß sich der Programmierer nicht mehr um diese Formalitäten kümmern muß.
Hinweis
Im Aufbau von 16- und 32-Bit-DLLs gibt es grundlegende Unterschiede. Da von Visual Basic-Programmen (ab Version 5.0) nur 32-Bit-DLLs aufgerufen werden können, werden 16-Bit-DLLs nur aus dem Grund behandelt, damit die prinzipiellen Unterschiede deutlicher werden. Betrachten Sie die folgenden Hinweise zu den 16-Bit-DLLs daher unter diesem Aspekt. Für die Programmierung mit Visual Basic 6.0 haben sie keine Bedeutung.
Jede 16 Bit Windows-DLL besteht aus mindestens drei Komponenten:
Die Aufgabe der Funktion LibMain
In der LibMain-Funktion wird die Initialisierung für die gesamte DLL durchgeführt. Sie erzeugt, wenn die DLL geladen wird, einen Einsprungspunkt für Windows. Benutzt die DLL globale Variablen, so werden diese in dieser Funktion vorbelegt. Müssen Windows-Klassen registriert werden, so geschieht dies ebenfalls in LibMain. Besitzt die DLL eine lokale Halde (und somit ein eigenens Datensegment), wird dieser hier initialisiert.
Beispiel
Die folgenden C-Befehle zeigen den allgemeinen Aufbauf von LibMain:
int Far pascal LibMain (handle hinstance,
WORD wDataSeg,
WORD cbHeapSpace,
Word LPSTR lpszCmdLind)
{
WNDCLASS DllClass;
Handle hGlobalVariable;
//Regristieren von Windows-Klassen
DllClass.style = CS_HREDRAW | CS_VREDRAW
RegisterClass(&DllClass);
//Initialisieren globaler Variablen
hGlobalVariable = GlobalAlloc(GMEM_MOVEABLE,
0xC00L)
If(cbHeapSize !=0) //Datensegment ist MOVEABLE
UnlockData(0);
return(1);
}
Die Parameter von
LibMain und ihre Bedeutung:
Parameter |
Beschreibung |
hInstance |
Identifiziert die DLL-Instanz. |
wDataSeg |
Bestimmt den Wert des Datensegmentregisters (DS). |
cbHeapSize |
Bestimmt die Haldengröße, die in der Moduldefinitionsdatei (Erweiterung .DEF) eingetragen (Die LibEntry-Routine in der Datei Libentry.obj benutzt diesen Wert zum Initialisieren der lokalen Halde). |
lpszCmdLine |
Zeigt zu einem nullterminierten String zur Kommandozeilen-Information. Dieser Parameter wird nur sehr selten in Verbindung mit DLLs benötigt. |
Rückgabewert |
True, wenn die Funktion erfolgreich ausgeführt wurde. |
Ein Wort zur Speicherverwaltung
Benutzen Sie in DLLs anstatt der normalen C-Funktionen zur Speicherverwaltung wie zum Beispiel malloc, _falloc, free, _ffree etc. unbedingt die entsprechenden die Windows-API-Funktionen (siehe Kapitel 15 des Visual Basic-Kompendiums). Obwohl Standardfunktionen der Laufzeitbibliothek bei der Kompilierung direkt durch die ensprechenden Windows-Funktionen ersetzt werden, ist es empfehlenswert, direkt auf die Windows-Speichermanagement-Funktionen zurückzugreifen. Mit der Funktion LocalAlloc wird lokaler Speicher auf der lokalen Halde angefordert, während die Funktion LocalLock einen Zeiger auf das lokale Speicherobjekt zurückgibt. Für dieses Tutorial ist zur Erzeugung der Beispiel-DLL nur das Makro UnlockData() von Bedeutung, welches in der Windows-Header-Datei Windows.h definiert ist. Dieses Makro erledigt die Haldenverwaltung das für Datensegment, welches das Attribut MOVABLE besitzt..
Die Aufgabe der WEP-Funktion
Das Gegenstück zur Funktion LibMain ist die WEP-Funktion (Windows Exit Procedure). Hier muß unter anderem der globale Speicherplatz, der für globale Variablen belegt wurde, wieder freigegeben werden. Hier muß auch die Registrierung von Windows-Klassen, die mit der API-Funktion RegisterClass erfolgte, über die Funktion UnRegisterClass wieder rückgängig gemacht werden.
Syntax
int FAR Pascal WEP(int nExitType)
Die Parameter und ihre Bedeutung:
Parameter |
Bedeutung |
nExitType |
Bestimmt, ob entweder Windows beendet wird oder nur die jeweilige. Der Parameter kann folgende Werte annehmen: WEP_SYSTEM_EXIT oder WEP_FREE_DLL. |
Rückgabewert |
Bei erfolgreichem Aufruf wird True zurückgegeben. |
Beispiel
Die folgenden C-Befehle zeigen den allgemeinen Aufbau einer WEP-Funktion:
int FAR Pascal WEP(int nExitType)
{
UnRegisterClass(DllClass, nExitType);
return(1);
}
Die Funktion
LibMain wird einmal aufgerufen, nämlich dann, wenn die DLL zum ersten Mal geladen wird. Benutzen mehrere Windows-Applikationen die DLL, wird nur noch ein interner Zähler inkrementiert, der festhält, wie viele Applikationen die DLL nutzen.Deklarieren, aber bitte richtig
Nachdem der Eintritts- und der Austrittspunkt, gewissermaßen der äußere Rahmen einer DLL, beschrieben ist, muß natürlich noch gezeigt werden, wie die eigentlichen DLL-Funktionen deklariert und implementiert werden. Jede zu implemtierende Funktion oder Prozedur besitzt eine Prototype-Deklaration, die ganz zu Beginn der DLL steht. Eine Prototype-Deklaration hat für eine Funktion folgenden allgemeinen Aufbau:
VarTyp
FAR PASCAL _export Name(VarTyp, ...)Die eigentlichen Funktion hat folgenden Aufbau:
VarTyp
FAR PASCAL _export Name(VarTyp VarName, ...){
return(Ergebnis)
}
16-Bit-DLL-Funktionen werden in einem C-Programm stets mit den Schlüsselwörtern
FAR und PASCAL deklariert. Dabei generiert das erste Schlüsselwort eine vollständige 32 Bit-Adresse für die Funktion. Dies ist notwendig, weil der Aufruf nicht aus dem eigenen Codesegment, sondern aus dem des aufrufenden Programmes erfolgt. Das zweite Schlüsselwort gibt die Aufrufkonvention an, also die Reihenfolge, in der vor dem Funktionsaufruf die Parameter auf den Stack abgelegt werden. Da Windows-Funktionen die Parameter von links nach rechts auf den Stack ablegen, werden sie prinzipiell mit der PASCAL-Konvetion definiert. Ohne das Schlüsselwort PASCAL würde der C-Compiler Befehle erzeugen, die die Parameter von rechts nach links und damit in der falschen Reihenfolge auf den Stack ablegen. Die PASCAL-Konvention wird hauptsächlich eingesetzt, weil sie zu einem etwas effizienteren Code führt. Wenn nämliche jede Funktion oder Prozedur selber für das Entfernen der übergebenen Parameter zuständig ist, werden weniger Befehle beim Aufruf der Funktion benötigt.Damit die Funktion oder Prozedur keine normale Routine bleibt, muß dem Linker mitgeteilt werden, daß es sich hier um eine zu exportierende Funktion handelt. Das Schlüsselwort _
_export macht die entsprechende Routine für andere Anwendungen verfügbar. Dabei wird der Name der Funktion oder Prozedur exportiert.Die folgenden Größen können über _
_export deklariert werden: Daten, Funktionen, Member Funktionen, Konstruktoren, Destruktoren und Operatoren.Hinweis
Bei Verwendung des Schlüsselwortes
__export geht der Linker davon aus, das das zu exportierende Symbol kein Aliasname istMöchte man diese Attribute verändern, kann dieses in der Moduldefinitionsdatei der DLL (Erweiterung
.DEF) unter dem Statement EXPORTS. Wie eine Moduldefinitionsdatei allgemein aufgebaut ist, wird weiter unten gezeigt.Die Umsetzung einer DLL
In diesem Abschnitt wird die Umsetzung sowohl einer 16- als auch einer 32-DLL Schritt für Schritt beschrieben. Da es nur um das Prinzip geht, handelt es sich um eine Mini-DLL. Prinzipiell spricht nichts dagegen, diese beliebig auszubauen und und um viele nützliche Funktionen zu erweitern.
Übung 1
Die folgende Übung enthält den C-Quellcode für eine kleine DLL, die das Zerlegen eines 16 Bit-Wortes in sein höherwertiges und sein niederwertigens Byte übernimmt. Diese Aufgabe gestaltet sich unter Visual Basic normalerweise nicht so trivial, wie man es erwarten könnte, da es keinen LoByte- und HiByte-Operator gibt. Für die Umsetzung der DLL wurde der Visual C++-Compiler Version 1.5 von Microsoft verwendet werden. Natürlich kann prinzipiell jeder andere 16 Bit-Windows Compiler zum Einsatz kommen, der in der Lage ist, eine Windows-DLL zu erzeugen. Das Beispiel ist so einfach gehalten, daß sich eine Portierung leicht gestalten läßt.
Schritt 1
Legen Sie unter der Visual C++-Oberfläche eine neue Datei mit der Bezeichnung
Bitshift.c ab und tragen Sie dort folgenden Code ein:/* BITSHIFT.C - Beispiel-DLL für Visual Basic Programme.
BITSHIFT.DLL wird vom Programm BITS verwendet */
#include <windows.h>
int FAR PASCAL _export HiByte( unsigned sInteger );
int FAR PASCAL _export LoByte( int sInteger );
int FAR PASCAL _export HiLoByte( int sHiInteger, int sLoInteger );
/* Diese Funktion ist der Bibliotheks-Eintrittspunkt.
Sie muß in jeder DLL vorhanden sein,
die für Visual Basic Programme eingesetzt werden soll. */
int FAR PASCAL LibMain( HANDLE hInstance, WORD wDataSeg,
WORD wHeapSize, LPSTR lpszCmdLine )
{
if ( wHeapSize > 0 )
UnlockData( 0 );
return( 1 );
}
/* Extrahieren des signifikantesten Bytes einer Integer-Zahl. */
int FAR PASCAL _export HiByte( unsigned sInteger )
{
return( sInteger >> 8 );
}
/* Extrahieren des unsignifikantesten Bytes einer Integer-Zahl. */
int FAR PASCAL _export LoByte( int sInteger )
{
return( sInteger & 0xFF );
}
/* Kombinieren von zwei Bytes zu einer Integer-Zahl. */
int FAR PASCAL _export HiLoByte( int sHiInteger, int sLoInteger )
{
return(( sHiInteger << 8 ) | ( sLoInteger & 0xFF ));
}
Der Aufbau ist dehnbar einfach und umfaßt neben den eigentlichen DLL-Funktionen
HiByte, LoByte und HiLoByte auch die Windows-Eintrittsfunktion LibMain und die Windows-Austrittsfunktion WEP.Der wesentliche Schritt zur Zerlegung eines 16 Bit-Wortes mit den Funktionen
HiByte bzw. LoByte besteht in der Verwendung des Shift-Operators >> bzw. <<. Zur Kontrolle wird das höherwertige und das niederwertige Byte wieder mit der Funktion HiLoByte zusammengesetzt, was mit Hilfe des UND-Operators und der Bit-Maske 0xFF geschieht.Schritt 2
Für die Erzeugung der Datei
Bitshift.dll wird neben der Quelldatei Bitshift.c auch die Moduldefinitionsdatei benötigt. Legen Sie unter der Visual C++-Oberfläche eine neue Datei mit der Bezeichnung Bitshift.def an und tragen Sie dort folgenden Code ein:; BITSHIFT.DEF
; Modul-Definitionsdatei
LIBRARY BITSHIFT
DESCRIPTION ' Beispiel für die Zerlegung eines 16 Bit-Wortes.
EXETYPE WINDOWS
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE SINGLE
HEAPSIZE 1024
Die einzelnen Einträge haben folgende allgemeine Bedeutung:
Option |
Bedeutung |
NONE |
Kein Datensegment vorhanden. Diese Option sollte bei Verwendung die einzige sein;. sie steht nur für Bibliotheken zur Verfügung. |
SINGLE |
Ein einziges Datensegment wird von allen Instanzen des Moduls verwendet; sie steht nur für Bibliotheken zur Verfügung. |
MULTIPLE |
Ein Datensegment existiert nur für eine Instanz eines Moduls; sie steht nur für zur Verfügung. |
PRELOAD |
Das Datensegment wird geladen, wenn das Modul zum ersten Mal geladen wird. |
FIXED |
Das Datensegment bleibt an einem festen Speicherplatz.. |
MOVEABLE |
Das Datensegment kann im Speicher bewegt werden, um den Speicher kompakt zu halten. |
Tabelle: Die möglichen Parameter des DATA-Segments
Option |
Bedeutung |
FIXED |
Das Codesegment bleibt an einem festen Speicherplatz.. |
MOVEABLE |
Das Datensegment kann im Speicher bewegt werden, um den Speicher kompakt zu halten. |
DISCARDABLE |
Das Codesegment entfernt werden, falls es nicht mehr benötigt wird. |
PRELOAD |
Das Codesegment wird geladen, wenn das Modul zum ersten Mal geladen wird. |
LOADONCALL |
Das Codesegment wird geladen, wenn es angefordert wird. Der Resource-Compiler (RC) kann diese Option überschreiben. |
Tabelle: Die Parameter des Codesegments
Über diese Statements muß der Linkvorgang gesteuert werden, damit vom Compiler eine DLL erzeugt wird.
Um aber endlich den Compilier- und Linkvorgang zu starten, muß ein Projekt (Erweiterung
.MAK) angelegt werden.Schritt 3
Öffnen Sie in der Visual C++-Umgebung ein neues Projekt vom Typ "Windows dynamik link library". Geben Sie dem Projekt die Bezeichnung
Bitshift.mak. Fügen Sie dem Projekt die Dateien Bitshift.c und Bitshift.def hinzu. Starten Sie den Compiler- und Linkvorgang über das Menükommando Build. Danach müßte sich der Linker mit der MeldungenInitializing...
Compiling
c:\msvc\bin\bit\bitpack.c
Linking
Creating import library
Binding resources
Creating browser database
BITSHIFT.DLL - 0 error(s), 0 warning(s)
melden.
Über das Kommando
EXEHDR <Pfad> BITSHIFT.DLL
das in der Kommandozeile eingeben wird, können die Einträge der Datei
Bitshift.dll eingesehen werden. Innerhalb der Exporttabelle werden die Namen der exportierten Funktionen exakt so angezeigt, wie sie innerhalb eines Visual Basic-Programms aufgerufen werden müssen.Schritt 4
Die DLL-Funktion wird aufgerufen. Sicherlich können Sie es vor Spannung kaum noch aushalten. Wird die DLL in einem Visual Basic-Programm auch funktionieren? Ein kleines Beispiel wird es an das Licht bringen. Als erstes benötigen wir wie immer eine
Declare-Anweisung:Private Declare Function HiByte Lib "bitshift.dll" (ByVal Wert As Integer) As Integer
Anschließend kann die selbstdefinierte Funktion wie jede andere DLL-Funktion aufgerufen werden:
Private Sub cmdAusführen_Click()
Dim Wert As Integer, HiByte As Byte, LoByte As Byte
Wert = txtEingabe.Text
HiByte = HiByte(Wert)
LoByte = LoByte(Wert)
End Sub
Bei der Übergabe des Wertes 4369 sollte der Wert des High-Bytes 255 und der des Low-Bytes 17 betragen. Wenn dieses bei Ihnen nicht der Fall sein, schauen Sie sich den Quelltext der DLL noch einmal in Ruhe an.
Hinweis
Unter Visual C++ Version 1.5 ist es nicht erforderlich, sich Gedanken über eine Make-Datei zu machen. Diese (sie trägt den Namen
Bitshift.mak) wird von Visual C++ automatisch generiert und sollte nicht verändert werden. Seien Sie froh, daß diese Aufgabe für Sie erledigt wird.Der allgemeine Aufbau von 32 Bit-DLLs
Die folgenden Beschreibungen gelten für 32-Bit-DLLs, die mit einem 32-Bit-Compiler, wie z.B. Visual C++ 4.x, erzeugt werden können. Jede 32 Bit Windows-DLL besteht aus mindestens zwei Routinen:
Die Aufgabe der Funktion DLLMain
Unter 32 Bit gibt es nur noch eine Funktion, sie trägt den Namen DLLMain, die Initialisierung und Entladen einer DLL übernimmt. Diese Funktion ist zudem optional. Wird sie nicht explizit angegeben, erzeugt der Compiler seine eigene Version von DLLMain, die lediglich ein True-Wert zurückgibt. DLLMain verwendet die WINAPI -Konvention und drei Parameter. Der folgende Code zeigt ein typisches Gerüst der DLLMain-Funktion:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
switch( ul_reason_for_call ) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
}
return TRUE;
}
Je nach Bedarf kann auf die einzelnen Ereignisse durch entsprechenden Code reagiert werden. Die Funktion gibt bei Erfolg einen True-Wert zurück. Wird hingegen ein False-Wert zurückgegeben, wird der Initialisierungsprozeß abgebrochen.
Der
ul_reason_for_call-Parameter zeigt an wozu die Funktion DLLMain aufgerufen wurde: Initialisierung oder Entladung, für einen Prozeß oder einen Thread. Tabelle 3 gibt die Bedeutung der möglichen Werte des ul_reason_for_call-Parameters an.
Wert |
Bedeutung |
DLL_PROCESS_ATTACH |
Ein neuer Prozeß versucht auf die DLL zuzugreifen während ein Thread bereits läuft. |
DLL_THREAD_ATTACH |
Ein neuer Thread versucht auf die DLL zuzugreifen, während bereits ein Thread läuft. |
DLL_PROCESS_DETACH |
Ein Prozeß wird von der DLL. |
DLL_THREAD_DETACH |
Einer der zusätzlichen Prozesse (nicht der erste) wird von der DLL getrennt. |
Tabelle: Die möglichen Werte für
ul_reason_for_callDer
lpReserved-Parameter ist für das System reserviert und sollte deshalb nicht vom Code beeinflußt werdenDie Deklaration einer 32-Bit-DLL-Funktion
Die Initialisierung einer 32-Bit-DLL ist nun bekannt. Jetzt stellt sich die Frage, wie die eigentliche DLL-Funktion bzw. DLL-Prozedur deklariert und aufgebaut sein muß. Dieses gestaltet sich unter 32 Bit gänzlich anders als unter 16 Bit.
Eine Prototype-Deklaration hat für eine Funktion folgenden allgemeinen Aufbau:
_declspec(dllexport) VarTyp WINAPI Name(VarTyp, )
Die eigentlichen Funktion hat folgenden Aufbau:
_declspec(dllexport) VarTyp WINAPI Name(VarTyp VarName, )
{
return(Ergebnis)
}
Beachten Sie bitte, daß die neue Aufrufkonvention WINAPI die bisherige FAR PASCAL-Konvention ersetzt. Das
_export-Schlüsselwort ist unter 32 Bit überflüssig geworden. Die Ablösung übernimmt das _declspec-Schlüsselwort. Dieses reduziert die Anzahl der Schlüsselwörter, was auch für das WINAPI-Schlüsselwort gilt. Der allgemeine Aufruf sieht nun wie folgt aus:_declspec(
Attribut) Variablen-DeklarationFür den Export einer Funktion bzw. Prozedur wird
dllexport als Attribut genommen. Weitere Erläuterungen sind an dieser Stelle nicht notwendig, denn um den Rest kümmern sich Compiler und Linker für Sie.Übung 2
Wie einfach sich die C-DLL aus Übung 1 unter 32 Bit umsetzen läßt zeigt das Beispielprogramm dieser Übung. Auch das Zerlegen eines 32-Bit-Wertes in eine untere und obere 16-Bit-Hälfte ist mit Visual Basic ein wenig kompliziert, da Visual Basic grundsätzlich das Vorzeichen mitverarbeitet. Grundlage für die Umsetzung der 32-Bit-DLL ist diesmal der Visual C++-Compiler Version 4.0. Natürlich kann jeder andere 32 Bit-Windows Compiler genommen werden. Das Beispiel ist so einfach gehalten, daß sich eine Portierung leicht gestaltet läßt.
Schritt 1
Legen Sie unter der Visual C++-Oberfläche eine Datei mit dem Namen
Bitshift32.c an und tragen Sie dort folgenden Code ein:#include <windows.h>
//Schlüsselwort zum Export neu zuweisen
#define DLLExport _declspec(dllexport)
// Prototyping der Funktionen
DLLExport long WINAPI HiByte(unsigned long);
DLLExport long WINAPI LoByte(long);
DLLExport long WINAPI HiLoByte(long,long);
// Ein- und Austiegspunkt für 32 Bit-DLL
BOOL WINAPI DllMain (HANDLE hInst,
ULONG ul_reason_for_call,
LPVOID lpReserved)
{
// Die Parameter werden nicht genutzt
UNREFERENCED_PARAMETER(hInst);
UNREFERENCED_PARAMETER(ul_reason_for_call);
UNREFERENCED_PARAMETER(lpReserved);
// Ein True wird zurückgegeben
return 1;
}
// Funktion zum separieren der höherwertigen Bytes eines 32 Bit-Wortes
DLLExport long WINAPI HiByte(unsigned long sLong)
{
return(sLong >> 16);
}
// Funktion zum separieren der niederwertigen Bytes eines 32 Bit-Wortes
DLLExport long WINAPI LoByte(long sLong)
{
return(sLong & 0xFFFF);
}
// Funktion zum Zusammensetzten eines 32 Bit-Wortes aus höher- und niederwertigen Bytes
DLLExport long WINAPI HiLoByte(long sHiLong, long sLoLong)
{
return((sHiLong << 16)|(sLoLong & 0xFFFF));
}
Der Aufbau ist denkbar einfach und umfaßt neben den eigentlichen DLL-Funktionen
HiByte, LoByte und HiLoByte auch die Windows-Ein- und Austrittsfunktion DLLMain.Der wesentliche Schritt zur Zerlegung eines 32 Bit-Wortes mit den Funktionen
HiByte bzw. LoByte besteht, wie auch bei der 16-Bit-Version, in der Verwendung des Shift-Operators >> bzw. <<. Zur Kontrolle werden die höherwertigen und niederwertigen Bytes wieder mit der Funktion HiLoByte zusammengesetzt, was mit Hilfe des UND-Operators und der Bit-Maske 0xFFFF geschieht.Schritt 2
Öffnen Sie in der Visual C++-Umgebung ein neues Projekt vom Typ "Windows Dynamik Link Library". Geben Sie dem Projekt den Namen
Bitshift32.mak (es sind lange Dateinamen erlaubt) und fügen Sie dem Projekt dann die Datei Bitshift32.c hinzu.Schritt 3
Starten Sie den Compiler- und Linkvorgang über das Menükommando
Build. Danach müßte sich der Linker mit der MeldungenCompiling
bitshift.c
Linking
BitShift32.dll - 0 error(s), 0 warning(s)
Das Ergebnis ist eine DLL mit dem Namen
BitShift32.Dll.Mit Hilfe des Programm
DumpBin.Exe kann die erzeugte DLL eingesehen werden. Führen Sie dazu folgende Kommandozeile aus:DumpBin /EXPORTS <Pfad> BitShift32.DLL
Die Exporttabelle besitzt den folgenden Inhalt:
File Type: DLL
Section contains the following Exports for BitShift32.dll
0 characteristics
2F4B0624 time date stamp Wed Feb 22 10:38:44 1995
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint name
1 0 _HiByte@4 (00001005)
2 1 _HiLoByte@8 (0000100A)
3 2 _LoByte@4 (0000100F)
Wesentlich ist hier, daß die Namen der exportierten Funktionen mit einem vorangestellten Unterstrich und einem nachfolgendem @-Zeichen gefolgt von der Länge der übergebenen Parameter versehen ist. Diese Tatsache muß bei der Deklaration unter Visual Basic berücksichtigt werden. Denn dort muß der Name exakt so übergeben werden, wie er in der Exporttabelle aufgeführt ist. Da dieses aber ziemlich kryptisch ist, kann mit dem Schlüsselwort
Alias unter Visual Basic ein passender Name ausgewählt werden. Per Default werden Funktionen bzw. Prozeduren nach der _stdcall-Konvention übergeben, welche den allgemeinen Aufbau des exportierten Namens beinhaltet:_
Funktionsname@Anzahl der Bytes aller übergebenen ParameterWer aber eigene Namen exportieren möchte, der muß in dem Visual C++-Projekt eine Moduldefinitionsdatei mit folgendem Aufbau hinzufügen:
LIBRARY BitShift32
EXPORTS HiByte
LoByte
HiLoByte
Die Namen, die in der Sektion
EXPORTS angegeben sind, werden auch so übergeben. Die Alias-Benennung fällt dann unter Visual Basic fort.Hinweis
Funktionsnamen sind unter 32-Bit-Windows "case sensitiv", d.h, achten Sie auf Groß- und Kleinschreibung.
Schritt 4
Auch die 32-Bit-Version soll natürlich sofort ausprobiert werden. Diesmal benötigen Sie allerdings die 32-Bit-Version von Visual Basic. Fügen Sie in den Allgemein-Teil einer Form folgende
Declare-Anweisung ein:Private Declare Function HiByte Lib "BitShift32.dll" (ByVal Wert As Long) As Long
Nun kann die Funktion wie gewohnt aufgerufen werden:
Private Sub cmdAusführen_Click()
Dim Wert As Long
Wert = txtEingabe.Text
lblHiByte.Caption = HiByte(Wert)
End Sub
Ein Wort zu Borland C++
Daß in diesem Tutorial der Visual C++-Compiler von Microsoft im Vordergrund steht, darf nicht so verstanden werden, daß es keine Alternativen gibt. Im Gegenteil, der Borland C++-Compiler ist ebenfalls ein hervorragendes Werkzeug, daß zu Zeiten von Windows 3.1 sogar als der führende Compiler galt. Die Erstellung von DLLs geht unter dem Borland C++-Compiler 4.52 für unerfahrenere Programmierer vielleicht noch ein wenig leichter als mit Visual C++, weil noch weniger Details beachtet werden müssen. Das folgende Listing zeigt den Aufbau der 32 Bit-DLL
BitShift32.Dll:# include <windows.h>
long _export WINAPI HiByte(unsigned long)
long _export WINAPI LoByte(long)
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDll, DWORD fdwReason,
LPVOID lpvRservesd)
{
return 1;
}
// Funktion zum separieren der niederwertigen Bytes eines 32 Bit-Wortes
long _export WINAPI HiByte(unsigned long sLong)
{
return(sLong >> 16);
}
// Funktion zum separieren der niederwertigen Bytes eines 32 Bit-Wortes
long _export WINAPI LoByte(long sLong)
{
return(sLong & 0xFFFF);
}
Anders als unter dem Mircosoft Visual C++-Compiler wird hier noch das
_export-Schlüsselwort verwendet. Die Funktionen werden mit denjenigen Namen exportiert, die im Code angegeben werden. Die Funktion DLLEntryPoint steht als Plazhalter für den Einstiegspunkt in die DLL. Wird dieser dem Linker nicht weiter spezifiziert, nimmt der Linker per Default die Funktion DLLMain als Einstiegspunkt, die bekanntlich auch den geordneten Austieg aus der DLL übernimmt.Neben Borland C++ kommen für das Erstellen von Windows-DLL unter anderem Symantec C++, Watcom C++, Delphi und sogar der Microsoft Makroassembler (ab Version 6.1) in Frage. Und es muß nicht unbedingt die neueste Version sein. Wenn Sie auf einem Computerflohmarkt den "Uralt" Microsoft C-Compiler 6.0 finden sollten, greifen Sie zu, denn auch mit diesem Compiler lassen sich DLLs programmieren. DLLs sind, anders als z.B. die ActiveX-Zusatzteuerelemente, keine neumodische Erfindung. DLLs gibt es seit dem es Windows gibt, denn sie sind das Fundament, auf dem das klassische Windows aufbaut. In Zukunft werden sie allerdings durch ActiveX-DLLs, die über ihre COM-Schnittstelle programmiert werden, abgelöst.
DLLs in Basic programmiert
Um geschwindigkeitsoptimierte DLLs für Visual Basic zu erstellen, sind Sie nicht unbedingt auf C++ oder Delphi angewiesen. Durch PB/DLL von PowerBasic lassen sich 16- und 32-Bit-DLLs in Basic erstellen, die auch über Inline-Maschinencode verfügen können. Damit wird eine schnellstmögliche Performance buchstäblich garantiert. Allerdings lassen sich nur jene Teile eines Visual Basic-Programms in DLLs umwandeln, die nicht auf Steuerelemente zugreifen. Ein Sortieralgorithmus, mathematische Funktionen oder eine komplexere Stringfunktion sind sicherlich die aussichtsreichsten Kandidaten. PB/DLL ist ein reines DOS-Programm, das keinerlei Hilfestellungen für die Umsetzung bietet und daher gewisse Grundkenntnisse bezüglich der Parameterübergabe erfordert. Es wird (meines Wissens nach) in Deutschland von Kirschbaum Software und Zoschke Data vertrieben.
Zusammenfassung
Die Programmierung einer DLL kommt immer dann in Frage, wenn ein Programm bezüglich seiner Performance verbessert werden soll, oder wenn z.B. Zugriffe auf den Arbeitsspeicher oder einzelne Hardwarekomponenten durchgeführt werden sollen, die Visual Basic nicht übernehmen kann. Dabei darf allerdings nicht vergessen werden, daß Geschwindigkeit stets relativ ist. Durch den Native-Compiler, der mit Visual Basic 5.0 eingeführt wurde, kann Visual Basic bereits selber interne Optimierungen vornehmen. Eine DLL bringt oft keine zusätzliche Geschwindigkeitssteigerung mehr. Erschwerend kommt hinzu, daß ein moderner C++-Compiler ein überaus komplexes Werkzeug ist (das allerdings aufgrund einer komfortablen Entwicklungsumgebung relativ leicht zu bedienen ist) und eine Sprache wie C++ im allgemeinen schwieriger zu erlernen ist als Visual Basic. Außerdem führt eine DLL zunächst eine potentielle Instabilität in das Programm ein, der Quellcode muß daher sorgfältig getestet werden. Ein Wundermittel gegen den "Performance-Blues" sind DLLs nicht. Seit Visual Basic 5.0 muß der Extraaufwand für die Programmierung besonders gut überlegt sein.