|
|
|
|
Programmieren mit C++Borland C++ BuilderSprachkonzepteVC++ 32-Bit DLL in BC++ einbinden
|
Seit der Version 4.0 von Visual C++ gibt es Probleme mit dem Linken von 32-Bit DLLs, die mit VC++ erstellt wurden, in BC++ Anwendungen. Wie lassen sich diese Probleme vermeiden oder umgehen?
| Frage | |
Das Problem ist auf Änderungen zurückzuführen, die Microsoft an der Definition von _cdecl und _stdcall vorgenommen hat.
| Lösung | |
In Programmen, die für Windows 32-Bit geschrieben werden, sind die Aufrufkonventionen PASCAL, WINAPI und CALLBACK als _stdcall definiert, während der Default der Sprache C weiterhin cdecl ist.
Mit _cdecl werden ausführbare Programme größer, weil der Stack durch den Aufrufer bereinigt wird. Ferner werden Funktionen mit variabler Augumentenanzahl unterstützt. Bei _stdcall hingegen ist die Funktion für das Aufräumen des Stacks verantwortlich. In beiden Fällen werden jedoch die Parameter in umgekehrter Reihenfolge, also von rechts nach links, auf den Stack gelegt.
Unterschied zwischen Borland und Microsoft
Funktionen werden in C- und C++-Programmen über interne Namen, sogenannte „decorated names“ angesprochen. Diese Namen generiert der Compiler als Symbole für die Funk-tionsaufrufe. De facto sind die Symbole Strings, die üblicherweise aus dem Funktionsnamen und ggfs. hinzugefügten Buchstaben oder geänderter Schreibweise bestehen.
Der Unterschied zwischen Borland und Microsoft _stdcall besteht in der Art und Weise, wie die internen Namen gebildet werden. Borland nimmt die _cdecl Namen und entfernt den führenden Unterstrich aus dem Namen. Dies hat zur Folge, daß beispielsweise eine Funktion acb(), die drei Integer-Parameter übernimmt, im Abschnitt EXPORTS der DLL als abc auftaucht – ohne führenden Unterstrich und ohne weitere Angaben.
Microsoft beläßt den Unterstrich vor dem Funktionsnamen und fügt ein @-Zeichen am Ende hinzu, auf das die Anzahl der für den Stack benötigten Bytes folgt. Die Größe des Stacks errechnet sich aus der Größe der Parameter, die auf das nächste Vielfache von vier aufgerundet und addiert werden. Für drei Integer-Parameter ergibt sich somit 12, so daß die oben genannte Funktion abc() im Abschnit EXPORT von Microsoft-DLLs wie folgt erscheint: _abc@12.
| _cdecl und _stdcall | |
Wird eine solche durch MS-VC++ erzeugte DLL mit einer Borland-Anwendung zusammengelinkt, resultiert ein Linker-Fehler „unbekanntes Symbol“. Dies liegt daran, daß Borlands Linker nach einer Funktion „abc“ sucht, die Microsoft-DLL jedoch eine Funktion namens „_abc@12“ exportiert.
| Linker-Fehler | |
In diesem Zusammenhang ist es interessant zu wissen, daß Microsoft die System-DLLs des Windows-Betriebssystems nicht mit diesen neuen Namenskonventionen erzeugt hat. Daher kann man auch OS-Importbibliotheken einlinken, jedoch nicht DLLs, die mit den neuen MS-Compilern erzeugt wurden. Die Windows-DLLs sind aber offensichtlich nicht so compiliert worden.
Problembewältigung
Der effizienteste Weg zur Beseitigung des Namenskonflikts besteht darin, einen Alias für den Funktionsnamen zu verwenden. Dazu wird der Ordinalwert der Funktion in der DLL benötigt.
| Windows-DLLs | |
Jede Funktion einer DLL erhält einen eindeutigen Ordinalwert zugewiesen, der zwischen 1 und 65.535 liegt. Wird dieser Wert nicht explizit zugewiesen, vergibt der Linker automatisch aufsteigende Werte in der Reihenfolge der Auflistung der Funktionen in der DEF-Datei.
Die Vergabe der Ordinalwerte erfolgt im Abschnitt EXPORTS der DEF-Datei. Wurden diese Werte nicht explizit spezifiziert, können sie mit dem Tool IMPDEF.EXE ermittelt werden.
Für die oben genannte Funktion abc() ergibt sich dann als DEF-Datei
| Ordinalwert |
LIBRARY DLLDEMO.DLL
EXPORTS
_abc@12 = abc @1
Hier entspricht die linke Seite der MS-Syntax, in der die 12 die Stackgröße angibt. Auf der rechten Seite bezeichnet @1 den Ordinalwert, als die Funktion Nummer 1.
Neben dem Vorteil, Namenskonflikte aufzulösen, verbessert das Aufrufen von Funktionen über ihren Ordinalwert auch die Ausführungsgeschwindigkeit, weil der Zugriff über Ordinalwerte schneller erfolgt, als der Vergleich von Strings beim Auffinden der Funktion.
Per Default verwendet Borland’s Linker keine Ordinalwerte. Um diese zu aktivieren, muß in der IDE in den 32-Bit Linker Optionen die entsprechende Option aktiviert werden. Wird statt dessen TLINK.EXE verwendet, so ist die Option „/o“ zur Aktivierung verwendet.
| DEF-Datei für DEMODLL | |
Aliase für Funktionsnamen in DEF-Dateien sind eine weitere Möglichkeit zur Vereinheitlichung von Aufrufschnittstellen. Dokumentiert ist diese Möglichkeit jedoch so gut wie gar nicht.
Die prinzipielle Vorgehensweise ist recht einfach: Im Abschnitt IMPORTS der zur Anwendung gehörenden DEF-Datei wird der Linker angewiesen, den Namen einer Funkti-on über eine Referenz aufzulösen.
Dazu kann dem Linker ein Aufrufname genannt werden, dem entweder ein spezieller Funktionsname oder ein Ordinalwert aus der entsprechenden DLL zugewiesen wird.
IMPORTS
AppFuncA = DLLfilename.DLLfuncname
AppFuncB = DLLfilename.nn
Im obigen Beispiel ist AppFuncA der Name der Funktion, der im Quellcode der Anwendung verwendet wird. Durch die Zuweisung in der DEF-Datei wird der Linker angewiesen, anstelle von AppFuncA die Funktion DLLfuncname aus der DLL DLLfilename zu verwenden. Alternativ kann anstelle des konkreten Funktionsnamens auch der Ordinalwert der Funktion in der DLL verwendet werden.
Für das eingangs genannte Beispiel der Funktion abc() könnte man also schreiben
IMPORTS
abc = DLLDEMO.1
Unter Verwendung eines Alias ist es egal, wie der Compiler die DLL erzeugt, da anhand der Oridnalnummer jederzeit die gewünschte Nummer über einen Alias-Namen adressierbar wird.
| Alias
|
|
|