Ich habe mich jetzt intensiver mit der impliziten Linkerei von Fremd-DLLs zu GCC Hauptprogrammen befasst. Die Ausgangssituation ist so, dass wir eine Closed Source k8055d.dll und eine Includedatei k8055d.h vom Hersteller einer USB-IO-Karte haben. Eigentlich haben wir mehrere k8055d.dll in verschiedenen Entwicklungsstadien. Da sich die Funktionsanzahl und die Nummerierung (Ordinale) in den verschiedenen DLLs unterscheiden, muss man penibel darauf achten, welche k8055d.dll man installiert hat und mit der man arbeitet. Ich beziehe mich im folgenden auf die k8055d.dll vom April 2004 mit 21 Funktionen. Also: Wie macht man eine 32-Bit Importlibrary, wenn man keine Objektdateien und keine Quelldateien hat? Diese Frage hat schon Microsoft bewegt... http://support.microsoft.com/default.aspx?scid=kb;en-us;131313 (Ob die dreifache 13 was zu sagen hat ;-) In dem dortigen Artikel sind zwei Methoden vorgestellt, die ich mit der k8055d.dll durchexerzieren werde. 1) Die DEF-Methode Hier werden mit dem VC-Tool DUMPBIN.EXE die Symbole aus der DLL ermittelt, um händisch eine DEF-Datei zu erstellen. Mit der DEF-Datei und dem VC-Tool LIB.EXE kann man dann die LIB Importlibrary erstellen Diese Methode empfiehlt Microsoft, wenn die Funktionen in der DLL als C-Funktionen aufgerufen werden. 2) Die STUB-Methode Hier wird die Includedatei zu einer Quelldatei umgeschrieben, und mit dem VC-Tool CL.EXE zu einer Ersatz (Stub) Objektdatei übersetzt. Aus der Objektdatei kann man mit dem VC-Tool LIB.EXE die LIB Importlibrary erstellen. Diese Methode empfiehlt Microsoft, wenn die Funktionen in der DLL nicht als C-Funktionen aufgerufen werden. Zunächst in die Praxis mit der DEF-Methode... ============================================= > DUMPBIN.EXE /EXPORTS k8055d.dll Microsoft (R) COFF Binary File Dumper Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. Dump of file k8055d.dll File Type: DLL Section contains the following exports for K8055D.dll 0 characteristics 0 time date stamp Thu Jan 01 01:00:00 1970 0.00 version 1 ordinal base 21 number of functions 21 number of names ordinal hint RVA name 14 0 00008DA4 ClearAllAnalog 9 1 00008EBC ClearAllDigital 15 2 00008D74 ClearAnalogChannel 10 3 00008E58 ClearDigitalChannel 20 4 00008BC4 CloseDevice 21 5 000088D4 OpenDevice 16 6 00008D44 OutputAllAnalog 17 7 00008D14 OutputAnalogChannel 4 8 00008CA4 ReadAll 18 9 00008C60 ReadAllAnalog 5 A 00009184 ReadAllDigital 19 B 00008C24 ReadAnalogChannel 3 C 00008F68 ReadCounter 6 D 000090AC ReadDigitalChannel 2 E 00008FD8 ResetCounter 12 F 00008DFC SetAllAnalog 7 10 00008F40 SetAllDigital 13 11 00008DCC SetAnalogChannel 1 12 0000901C SetCounterDebounceTime 8 13 00008EE4 SetDigitalChannel 11 14 00008E28 WriteAllDigital Summary 1000 .edata 1000 .idata 1000 .reloc 1000 .rsrc 1000 BSS 9000 CODE 1000 DATA Die Namen werden händisch in eine DEF-Datei k8055d.def übertragen... LIBRARY k8055d.dll EXPORTS ClearAllAnalog ClearAllDigital ClearAnalogChannel ClearDigitalChannel CloseDevice OpenDevice OutputAllAnalog OutputAnalogChannel ReadAll ReadAllAnalog ReadAllDigital ReadAnalogChannel ReadCounter ReadDigitalChannel ResetCounter SetAllAnalog SetAllDigital SetAnalogChannel SetCounterDebounceTime SetDigitalChannel WriteAllDigital Und mit dem VC-Tool LIB.EXE die Importlibrary erstellt... >LIB /machine:ix86 /DEF:k8055d.def Microsoft (R) Library Manager Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. Creating library k8055d.lib and object k8055d.exp > make gcc -c calldll.c gcc -mno-cygwin -o calldll.exe calldll.o k8055d.lib calldll.o:calldll.c:(.text+0x79): undefined reference to `__imp__OpenDevice@4' calldll.o:calldll.c:(.text+0xa8): undefined reference to `__imp__ClearAllDigital@0' calldll.o:calldll.c:(.text+0xb5): undefined reference to `__imp__WriteAllDigital@4' calldll.o:calldll.c:(.text+0xdc): undefined reference to `__imp__CloseDevice@0' collect2: ld returned 1 exit status make: *** [calldll.exe] Error 1 ;-( Woher kommen die fehlenden Symbole bzw. wieso werden die vom Linker nicht in der Importlibrary gefunden? Die @x werden vom GCC in die Objektdatei vom Hauptprogramm eingefügt, weil die Aufrufkonvention __stdcall verwendet wurde. Bei dieser Aufrufkonvention wird gekennzeichnet, wieviel Platz durch die Funktionsargumente benötigt wurde... Kein Argument @0, ein Argument @4, zwei Argumente @8... Jetzt erforschen wir die Importlibrary. >strings k8055d.lib ! / 1138749658 0 1086 ` __IMPORT_DESCRIPTOR_k8055d __NULL_IMPORT_DESCRIPTOR k8055d_NULL_THUNK_DATA _ClearAllAnalog __imp__ClearAllAnalog ... In der Importlibrary stecken die Symbole ohne die @x an den Funktionsnamen drin. Wieso tragen die Symbole in der k8055d.dll keinen @x Anhang, wenn bei der Erstellung der DLL die gleiche Aufrufkonvention benutzt wurde? Vor dem weiteren Erforschen dieser Frage geht es zunächst zur STUB-Methode... In die Praxis mit der STUB-Methode... ===================================== Zunächst wird die Includedatei in eine Quelldatei mit Dummyfunktionen umgeschrieben. #ifdef __cplusplus extern "C" { #endif typedef unsigned char bool; #define FUNCTION __declspec(dllexport) FUNCTION long __stdcall OpenDevice(long CardAddress){return 0L;} FUNCTION __stdcall CloseDevice(){} FUNCTION long __stdcall ReadAnalogChannel(long Channel){return 0L;} FUNCTION __stdcall ReadAllAnalog(long *Data1, long *Data2){} FUNCTION __stdcall OutputAnalogChannel(long Channel, long Data){} FUNCTION __stdcall OutputAllAnalog(long Data1, long Data2){} FUNCTION __stdcall ClearAnalogChannel(long Channel){} FUNCTION __stdcall ClearAllAnalog(){} FUNCTION __stdcall SetAnalogChannel(long Channel){} FUNCTION __stdcall SetAllAnalog(){} FUNCTION __stdcall WriteAllDigital(long Data){} FUNCTION __stdcall ClearDigitalChannel(long Channel){} FUNCTION __stdcall ClearAllDigital(){} FUNCTION __stdcall SetDigitalChannel(long Channel){} FUNCTION __stdcall SetAllDigital(){} FUNCTION bool __stdcall ReadDigitalChannel(long Channel){return (bool)0;} FUNCTION long __stdcall ReadAllDigital(){return 0L;} FUNCTION long __stdcall ReadCounter(long CounterNr){return 0L;} FUNCTION __stdcall ResetCounter(long CounterNr){} FUNCTION __stdcall SetCounterDebounceTime(long CounterNr, long DebounceTime){} #ifdef __cplusplus } #endif Und die wird mit dem VC-Tool CL.EXE zu einer Objektdatei übersetzt. >CL /c k8055d.c Microsoft (R) 32-bit C/C++ Standard Compiler Version 12.00.8168 for 80x86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved. k8055d.c Und dann wird aus der Objektdatei mit dem VC-Tool LIB.EXE die Importlibrary erzeugt. >LIB /machine:ix86 /NODEFAULTLIB /DEF: k8055d.obj Microsoft (R) Library Manager Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. Creating library k8055d.lib and object k8055d.exp Damit einen GCC Kompilierlauf gestartet... > make gcc -c calldll.c gcc -mno-cygwin -o calldll.exe calldll.o k8055d.lib Oha! Keine fehlenden Symbole mehr beim Linken! >strings k8055d.lib ! / 1138750587 0 1134 ` D__IMPORT_DESCRIPTOR_k8055d __NULL_IMPORT_DESCRIPTOR k8055d_NULL_THUNK_DATA _OpenDevice@4 __imp__OpenDevice@4 ... Aber das "böse" Erwachen beim Programmlauf... >./calldll 255 "Die Datei CALLDLL.EXE ist verknüpft mit dem fehlenden EXPORT-K8055D.DLL:_OpenDevice@4" Da ist uns eigentlich lieber, wenn der Linker mosert... Also zurück zur DEF-Methode. Wieso sind die @x bei den Symbolen in k8055d.dll gestrippt? Bzw. wie kann man den GCC überzeugen die @x für die importierten Funktionen aus der DLL ebenfalls zu strippen? Der naheliegende Weg, z.B. indem man den __stdcall entfernt, ist keine Lösung. Wenn man das macht, stimmt die Stackkorrektur nach dem Funktionsaufruf nicht mehr. Denn auch die Funktionen in der k8055d.dll sind definitiv mit __stdcall übersetzt. Dann kracht es irgendwann... Die Dummyfunktion OpenDevice in dem STUB, welches mit __stdcall übersetzt wurde in Assemblercode... seg000:00000341 push ebp seg000:00000342 mov ebp, esp seg000:00000344 xor eax, eax seg000:00000346 pop ebp seg000:00000347 retn 4 Man sieht, dass das Argument der Funktion von der Funktion selbst vom Stack entfernt wird (retn 4), so wie es vom __stdcall Aufruf auch erwartet wird. Der entsprechende Rücksprung in k8055d.dll sieht so aus: CODE:00408B73 mov esp, ebp CODE:00408B75 pop ebp CODE:00408B76 retn 4 CODE:00408B76 OpenDevice endp D.h. ebenfalls ein __stdcall Rücksprung. Oder kann man dem GCC Linker LD verständlich machen, dass die Namen mit @x in der DLL ohne @x vorhanden sind? Bzw. kann man in der DEF-Datei angeben, dass ein Aliasname ohne @x in der DLL verwendet werden soll? Das geht, wenn man in der DEF Datei zu dem Symbol, das der Linker sehen soll die Ordinale angibt, die die betreffende Funktion in der DLL hat. LIBRARY k8055d.dll EXPORTS ClearAllAnalog@0 @14 ClearAllDigital@0 @9 ClearAnalogChannel@4 @15 ClearDigitalChannel@4 @10 CloseDevice@0 @20 OpenDevice@4 @21 OutputAllAnalog@8 @16 OutputAnalogChannel@8 @17 ReadAll@0 @4 ReadAllAnalog@8 @18 ReadAllDigital@0 @5 ReadAnalogChannel@4 @19 ReadCounter@0 @3 ReadDigitalChannel@4 @6 ResetCounter@4 @2 SetAllAnalog@0 @12 SetAllDigital@0 @7 SetAnalogChannel@4 @13 SetCounterDebounceTime@8 @1 SetDigitalChannel@4 @8 WriteAllDigital@4 @11 Leider kenne ich keinen Weg statt der Nummer den alternativen Funktionsnamen in der DLL anzugeben. Der naheliegende Weg über Aliase (z.B. ClearAllAnalog@0=ClearAllAnalog) funktioniert nicht. OK. Nun zu etwas ganz anderem... Eigentlich sollen die GCC-Tools statt der VC-Tools eingesetzt werden. Wie kann man mit den GCC-Tools eine Importlibrary machen? ========================================================= Das geht mit Hilfe DLLTOOL und einer passenden DEF-Datei. Die passende DEF-Datei kann man händisch erstellen bzw. aus der obigen DEF-Datei ableiten (s. oben und auskommentierter Weg unten) oder automatisch aus einer STUB-Objektdatei machen lassen. Hier ein makefile zum Durchexerzieren der verschiedenen Verfahren... CC_OPTIONS = LD_OPTIONS = -mno-cygwin MSVCBINDIR = d:/programmieren/microsoft\ visual\ studio/vc98/bin calldll.exe : calldll.o k8055d.lib k8055d.a makefile gcc $(LD_OPTIONS) -o calldll_vc.exe calldll.o k8055d.lib # Linken über libk8055d.a gcc $(LD_OPTIONS) -o calldll_gcc.exe calldll.o -L. -l k8055d # Alternativ Linken über k8055d.a # gcc $(LD_OPTIONS) -o calldll_gcc.exe calldll.o k8055d.a # Je nachdem was getestet werden soll... cp calldll_gcc.exe calldll.exe calldll.o : calldll.c k8055d.h makefile gcc $(CC_OPTIONS) -c calldll.c k8055d.lib : k8055d.def makefile $(MSVCBINDIR)/lib /machine:ix86 /def:k8055d.def rm k8055d.exp # Über die händisch erstellt DEF-Datei # k8055d.a : k8055d.def makefile # cat k8055d.def | sed 's/\(.*@[0-9]*\).*@[0-9]*/\1/' > tmp_k8055d.def # dlltool --kill-at --def tmp_k8055d.def --output-lib k8055d.a # cp k8055d.a libk8055d.a # Über eine DEF-Datei aus der STUB-Objektdatei k8055d.a : k8055d.c k8055d.h makefile gcc -c k8055d.c dlltool k8055d.o --output-def stub_k8055d.def echo LIBRARY k8055d.dll > tmp_k8055d.def cat stub_k8055d.def | sed 's/\(.*@[0-9]*\).*@[ 0-9]*/\1/' >> tmp_k8055d.def dlltool --kill-at --def tmp_k8055d.def --output-lib k8055d.a cp k8055d.a libk8055d.a clean : rm libk8055d.a k8055d.a rm calldll.o k8055d.o k8055d.obj rm stub_k8055d.def tmp_k8055d.def k8055d.exp Das SED Kommando schnippelt in der DEF-Datei die @x Ordinale ab, d.h. jetzt wollen wir definitiv über die Funktionsnamen gehen! Der Parameter -k weist DLLTOOL an, an den Symbolen das @x abzuschneiden. Stimmt das? Erst tragen wir die @x händisch bzw. automatisch in der DEF-Datei ein und jetzt schneiden wir sie ab? Das passt schon. Die @x an den Funktionsnamen werden zuerst benutzt, um die <__IMP__funktionsname@x> Symbole zu erzeugen, dann erst werden die @x entfernt um die Symbole in die Importlibrary zu schreiben. --- Puh. Das war ein langes Stück Arbeit mit ein paar hässlichen Abstürzen. Zusammenfassend meine Meinung: Implizites Linken geht, aber Explizites Linken ist mir 1000x lieber. Als nächstes steht ein Debuggen der Herstellersoftware an. Wenn man sehr oft Calldll hintereinander aufruft, kommt irgendwann nach ca. 100-200x eine Meldung, dass K8055Ex.EXE einen Fehler produziert hat und anschliessend eine Meldung, dass keine FASTTimer mehr vorhanden sind. Anschliessend stürzt sich der Rechner richtig tief ins Nirvana. Die FASTTimer-Geschichte ist eine Sache innerhalb der Herstellersoftware... Gruß Stefan