Einsprungpunkt-DLLs mit Visual Basic
von Tobias Koch
Einleitung
In diesem Tutorial wird erläutert, wie man ohne die Hilfe von teuren AddIns Einsprungpunkt-DLLs mit Visual Basic erstellen kann. Zusätzlich wird kurz erläutert, welche versteckten Möglichkeiten sich noch bieten. Außerdem enthält dieses Tutorial ein Beispiel für eine DLL, die zwei Funktionen exportiert und ein Beispielprojekt, das die Verwendung dieser DLL veranschaulicht.
Update (29.02.2004):
Das zum Download angebotene Beispiel wurde überarbeitet.
Mit freundlichen Grüßen
Tobias Koch TobiKochFN@T-Online.de
Allgemeines
Bevor wir WinAPI-DLLs erstellen können, müssen wir verstehen, wie der Visual Basic Compiler funktioniert bzw. wie man in anderen Programmiersprachen (z.B. C++) solche Dateien erzeugt. Wenn Sie im Menü des VB Editors "Kompilieren" wählen, übersetzt der Editor den von Ihnen getippten Code in eine Zwischensprache. Diese Dateien werden an einen zweiten Compiler übergeben (c2.exe), der daraus Objektmodule (*.obj) erstellt. Diese wiederum werden vom Linker (link.exe) zusammen gefügt, d.h. eine ausführbare Datei (*.exe, *.dll, *.ocx) wird erstellt. Wenn wir eine WinAPI-DLL erstellen möchten, müssen wir also ein eigenes Programm zwischen c2.exe und link.exe schalten, das die dem Linker übergebenen Befehlszeilenparameter entsprechend abändert.
Grundlagen
Wenn wir den Linker mit einem eigenen Programm ersetzen, das die übergebenen Befehlszeilenparameter anzeigt, können wir verstehen, wie der Linker aus mehreren Objektmodulen ein Programm erzeugt:
"C:\Test\Module11.OBJ" "C:\Test\Test1.OBJ" "C:\Programme\Visual Basic\VBAEXE5.LIB" /ENTRY:__vbaS /OUT:"C:\Test\Test.exe" /BASE:0x400000 /SUBSYSTEM:WINDOWS,4.0 /VERSION:1.0 /INCREMENTAL:NO /OPT:REF /MERGE:.rdata=.text /IGNORE:4078
Nun betrachten wir die Befehlszeilenparameter des Linkers:
Microsoft (R) 32-Bit Incremental Linker Version 4.20.6164 Copyright (C) Microsoft Corp 1992-1996. All rights reserved. usage: LINK [options] [files] [@commandfile] options: /ALIGN:# /BASE:{address|@filename,key} /COMMENT:comment /DEBUG /DEBUGTYPE:{CV|COFF|BOTH} /DEF:filename /DEFAULTLIB:library /DLL /DRIVER[:UPONLY] /ENTRY:symbol /EXETYPE:DYNAMIC /EXPORT:symbol /FIXED /FORCE[:{MULTIPLE|UNRESOLVED}] /GPSIZE:# /HEAP:reserve[,commit] /IMPORT:[symbol][,][LIB=container][,WEAK=1] /IMPORT:[CURRENTVER=#][,][OLDCODEVER=#][,][OLDAPIVER=#] /IMPLIB:filename /INCLUDE:symbol /INCREMENTAL:{YES|NO} /LIBPATH:path /MAC:{BUNDLE|NOBUNDLE|TYPE=xxxx|CREATOR=xxxx|INIT=symbol|TERM=symbol} /MAC:{MFILEPAD|NOMFILEPAD} /MACDATA:filename /MACHINE:{IX86|MIPS|ALPHA|PPC|M68K|MPPC} /MACRES:filename /MAP[:filename] /MERGE:from=to /NODEFAULTLIB[:library] /NOENTRY /NOLOGO /OPT:{REF|NOREF} /ORDER:@filename /OUT:filename /PDB:{filename|NONE} /PROFILE /RELEASE /SECTION:name,[E][R][W][S][D][K][L][P][X] /SHARED /STACK:reserve[,commit] /STUB:filename /SUBSYSTEM:{NATIVE|WINDOWS|CONSOLE|POSIX}[,#[.##]] /SWAPRUN:{NET|CD} /VERBOSE[:LIB] /VERSION:#[.#] /VXD /WARN[:warninglevel] /WS:AGGRESSIVE
Auf die Bedeutung dieser Parameter möchte ich nicht näher eingehen, hier finden Sie eine genaue Beschreibung:
MSDN Linker Reference
Die Parameter, die uns interessieren, sind "/DLL" und "/DEF:FileName". Durch "/DLL" wird angegeben, dass eine Bibliothek erstellt werden soll, durch die zweite Option können wir eine Exportdefinitionsdatei angeben, mit deren Hilfe wir bestimmen können, welche Funktionen exportiert werden sollen.
Linkercontroller
Nun müssen wir ein Programm entwickeln, dass zwischen dem zweiten Compiler und dem Linker aufgerufen wird und somit die vom IDE übergebenen Befehlszeilenparameter abändert. Erstellen Sie ein neues Projekt und fügen Sie diesem ein Formular (frmMain) und ein Modul (modFunctions) ein. In das Modul müssen Sie einen Teil des Codes des Tipps 148 einfügen:
'Dieser Source stammt von http://www.activevb.de 'und kann frei verwendet werden. Für eventuelle Schäden 'wird nicht gehaftet. 'Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum. 'Ansonsten viel Spaß und Erfolg mit diesem Source! '------------- Anfang Projektdatei Project1.vbp ------------- '--------- Anfang Formular "Form1" alias Form1.frm --------- 'Control CommandButton: Command1 Option Explicit 'Diverse APIs deklarieren Private Declare Function CreateProcess Lib "Kernel32" Alias _ "CreateProcessA" ( _ ByVal lpAppName As Long, _ ByVal lpCmdLine As String, _ ByVal lpProcAttr As Long, _ ByVal lpThreadAttr As Long, _ ByVal lpInheritedHandle As Long, _ ByVal lpCreationFlags As Long, _ ByVal lpEnv As Long, _ ByVal lpCurDir As Long, _ lpStartupInfo As STARTUPINFO, _ lpProcessInfo As PROCESS_INFORMATION _ ) As Long Private Declare Function WaitForSingleObject Lib "Kernel32" ( _ ByVal hHandle As Long, _ ByVal dwMilliseconds As Long _ ) As Long Private Declare Function CloseHandle Lib "Kernel32" ( _ ByVal hObject As Long _ ) As Long 'Einige Konstanten benennen Private Const NORMAL_PRIORITY_CLASS = &H20& Private Const INFINITE = -1& 'Einige Datentypen erstellen Private Type STARTUPINFO cb As Long lpReserved As String lpDesktop As String lpTitle As String dwX As Long dwY As Long dwXSize As Long dwYSize As Long dwXCountChars As Long dwYCountChars As Long dwFillAttribute As Long dwFlags As Long wShowWindow As Integer cbReserved2 As Integer lpReserved2 As Integer hStdInput As Long hStdOutput As Long hStdError As Long End Type Private Type PROCESS_INFORMATION hProcess As Long hThread As Long dwProcessID As Long dwThreadID As Long End Type Public Function ShellWait(cmdline As String, Optional ByVal _ bShowApp As Boolean = False) As Boolean 'Diese Funktion führt einen Befehl (in CmdLine) aus. 'Dabei wird das sich öffnende Fenster unsichtbar gemacht. 'Diese Funktion wird erst beendet, wenn der Befehl 'vollständig abgearbeitet ist. 'Speicher reservieren Dim uProc As PROCESS_INFORMATION Dim uStart As STARTUPINFO Dim lRetVal As Long 'Die Datentypen initialisieren uStart.cb = Len(uStart) uStart.wShowWindow = Abs(bShowApp) uStart.dwFlags = 1 'Fenster erzeugen lRetVal = CreateProcess(0&, cmdline, 0&, 0&, 1&, _ NORMAL_PRIORITY_CLASS, 0&, 0&, _ uStart, uProc) 'Warten, bis Fenster beendet wurde lRetVal = WaitForSingleObject(uProc.hProcess, INFINITE) 'Fenster schließen lRetVal = CloseHandle(uProc.hProcess) lRetVal = CloseHandle(uProc.hThread) 'Rückgabewert setzen ShellWait = (lRetVal <> 0) End Function
Falls Sie VB 5 nutzen, müssen Sie in das Modul zusätzlich eine Replace-Funktion einfügen, da wir diese später benötigen:
(Code von http://www.vb-tec.de/replace.htm)
Positionieren Sie auf dem Formular nun eine Textbox (txtCompiler) und einen Button (CmdOk) und fügen Sie den folgenden Code ein:
Private Sub CmdOk_Click() Dim CmdLine As String CmdLine = Command() CmdLine = Replace(CmdLine, "/ENTRY:__vbaS", "/ENTRY:DLLMain") CmdLine = Replace(CmdLine, "/BASE:0x400000", "/BASE:0x10000000") CmdLine = CmdLine + " /DLL " + "/DEF:" + """" + txtCompiler.Text + """" ShellWait App.Path + "\link1.exe " + CmdLine Unload Me End Sub Private Sub Form_Load() If MsgBox("Linker-Controller starten?", vbYesNo) = vbNo Then ShellWait App.Path + "\link1.exe " + Command() Unload Me End If End Sub
Es muss die "ShellWait" Funktion verwendet werden, da das IDE die erstellten Objektdateien nach dem Kompilieren sofort wieder löscht und es dann zu einem Fehler kommt.
Nun kommt der kritische Teil: Erstellen Sie das Programm "Link.exe", gehen Sie danach in Ihr VB Verzeichnis und benennen Sie die Datei "Link.exe" in "Link1.exe" um, danach müssen Sie das von Ihnen erstellte Programm "Link.exe" in dieses Verzeichnis kopieren.
Wenn Sie nun im IDE das Projekt kompilieren, wird eine Meldungsbox gezeigt. Falls Sie "Nein" klicken, wird eine normale EXE erzeugt, ansonsten wird das Formular angezeigt.
Beispiel
Erstellen Sie ein neues Projekt und fügen Sie ein Modul (modFunctions) mit dem folgenden Code ein:
Function DLLMain(ByVal a As Long, ByVal b As Long, ByVal c As Long) As Long DLLMain = 1 End Function Sub Main() ' Dummy End Sub Function Subtrahieren(ByVal A As Double, ByVal B As Double) As Double Subtrahieren = A - B End Function Function Addieren(ByVal A As Double, ByVal B As Double) As Double Addieren = A + B End Function
Die Prozedur "Main" müssen wir einfügen, damit die IDE beim Kompilieren keinen Fehler ausgibt. Sie können natürlich auch einen Code in diese Prozedur einfügen, dieser wird dann beim Laden und Entladen der DLL ausgeführt.
Nun müssen wir eine Exportdefinitionsdatei erstellen. Diese Datei gibt an, welche Funktionen exportiert werden sollen. Erstellen Sie eine Datei namens "C:\Export.def", öffnen Sie diese mit dem Texteditor und geben Sie die folgenden Zeilen ein:
LIBARY Test EXPORTS Subtrahieren Addieren
Hinter das Schlüsselwort "LIBRARY" müssen Sie den Namen der DLL schreiben, unter "EXPORTS" werden die exportierten Funktionen aufgelistet. Weitere Informationen über .DEF-Dateien erhalten Sie hier:
MSDN Modul-Definition (.DEF) Dateien
Kompilieren Sie nun das Projekt (Dateiname: "DLL.dll") und geben Sie in die Textbox des Linker-Controllers den Pfad und Dateinamen der .DEF-Datei ein (ohne Anführungszeichen) und klicken Sie auf den Button. Nun wird eine Bibliothek erstellt, die zwei Funktionen exportiert.
Dies lässt sich mit einem Programm wie z.B. dem Dependency Walker (www.dependencywalker.com) überprüfen:
DLL-Aufruf
Erstellen Sie eines neues Projekt fügen Sie ein Modul (modProgram) hinzu, speichern Sie dieses dann im selben Verzeichnis wie die erstellte DLL und fügen Sie den folgenden Code ein:
Declare Function Subtrahieren Lib "DLL.dll" (ByVal A As Double, ByVal B As Double) As Double Declare Function Addieren Lib "DLL.dll" (ByVal A As Double, ByVal B As Double) As Double Sub Main() MsgBox CStr(Addieren(3.3, 2.7)) MsgBox CStr(Subtrahieren(3.3, 2.7)) End Sub
Bekannte Probleme
Leider treten bei DLLs einige kleine Fehler auf:
- Fehlermeldungen beim Versuch, innerhalb der exportierten Funktionen Instanzen von Klassen zu erstellen
- Probleme bei Funktionen, die als Parameter einen String erwarten oder diesen zurück geben (benutzen Sie statt dessen den Datentyp Variant)
- Bei VB6 werden die Bibliotheken teilweise nicht gefunden (fügen Sie die Declare-Statements in ein Modul ein)
Vielen Dank an Andreas Kolberg für seine Hilfe!
Schlusswort
Natürlich können Sie den Linker-Controller noch beliebig erweitern. Durch die Angabe des Parameters "/SUBSYSTEM:CONSOLE" wird z.B. eine Konsolenanwendung erstellt. Alle Möglichkeiten des Linkers ausführlich zu dokumentieren wäre eher ein Thema für ein Buch mit mehreren hundert Seiten, nicht jedoch für ein Tutorial.
Projekt als Download [147850 Bytes]
Ihre Meinung
Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.