Projects
unter Windows
Für ein Projekt, das
unter BC 5.02 geschrieben war sollte ich den Microsoft MediaView 1.4.1 kapseln. Der MediaView ist für das Layouten und Darstellen für Windows-Hilfe ähnliche Dateien gut (gewisse Ähnlichkeiten weisen darauf hin, das
dies sogar ein und dasselbe System ist) und besteht im technischen Sinn einfach aus einer Sammlung von DLL’s mit durch einen Header definierten API.
So weit so gut... Die Beispielprogramme waren für den
Microsoft C-Compiler geschrieben - aber C/C++ Compiler sollten ja kompatibel sein und mit DLL’s kann jede Windows-Programmiersprache umgehen.
So machte ich mich ans Werk und ein grosser Teil ging auch recht gut...
Ab und zu stürzte mein Programm einfach ab und nach einigem Debuggen kam ich zu einer Funktion die wie folgt definiert war:
POINT EXPORT_API FAR PASCAL ptMVGetSize(LPMV lpMv); |
die Funktion lieferte kein korrektes Ergebniss und wie Testläufe zeigten war der Stack nach dem Aufruf anders als er sein sollte. Da ich das
Projekt fertigstellen musste wurde ein Debugging fällig - bis hinunter auf die Assemblerebene...
Eine Calling Convention ist eine
Definition wie eine Funktion ihre Parameter auf dem Stack übergeben bekommt, welche Reihenfolge die Parameter haben und wer den Stack hinterher wieder aufräumt. In einem Programm sind die Calling Conventions
meist nicht so wichtig - zur Kommunikation über Programmgrenzen hinweg (z.B. zum Einsprung in eine DLL) ist jedoch eine allgemeingültige Definition notwendig!
Unter Windows gibt es grundlegend drei Calling-Konventions wie folgende Tabelle zeigt:
__cdecl
|
Parameter von rechts nach links auf den Stack - Aufrufer löscht den Stack
int __cdecl foo(int a, int b) // Borland Example
|
push ebp ; Function Prolog (save ebp)
|
mov ebp,esp ; Function Prolog (Stackframe)
|
{ return 3
|
mov eax,0x00000003 ; Return value to eax
|
}
|
pop ebp ; Function Epilog
|
ret ; Return
|
|
int c=foo(1,2) // Aufruf (Borland)
|
push 0x02 ; last Parameter to Stack
|
push 0x01 ; first Parameter to Stack
|
call foo(int,int) ; call Function
|
add esp,0x08 ; clean Stack
|
|
|
// in the optimized version the vc-code is much smaller!!!
|
int __cdecl foo(int a,int b) // Microsoft Example (unoptimized!)
|
{ return 3;
|
push ebp ; Function Prolog
|
mov ebp,esp ; Function Prolog (Stackframe)
|
sub esp,40h ; ???
|
push ebx ; Save ebx
|
push esi ; Save esi
|
push edi ; Save edi
|
lea edi,[ebp-40h] ; !! Laut tkrammer@3rd-evolution.de
|
mov ecx,10h ; !! debug-code das Objekt wird mit
|
mov eax,0CCCCCCCCh ; !! 0xCCCCCCCC - werten initialisiert
|
rep stos dword ptr [edi] ; !!
|
mov eax,3 ; Return Value in eax
|
}
|
pop edi ; Destroy edi and correct Stack
|
pop edi ; Restore edi
|
pop esi ; Restore esi
|
pop ebx ; Restore ebx
|
mov esp,ebp ; Restore old Stack
|
pop ebp ; Restore old Stackframe
|
ret
|
int c=foo(1,2); // Aufruf
|
push 2 ; last Parameter to Stack
|
push 1 ; first Parameter to Stack
|
call @ILT+15(test) ; call Function
|
add esp,8 ; Clean Stack
|
mov dword ptr [ebp-4],eax ; Use return - Value
|
|
__stdcall
|
Parameter von rechts nach links auf den Stack - Funktion räumt den Stack auf
int __stdcall foo(int a, int b) // Borland Example
|
push ebp
|
mov ebp,esp
|
{ return 3;
|
eax,0x00000003
|
}
|
pop ebp
|
ret 0x0008
|
|
int c=foo(1,2); // Aufruf
|
push 0x02
|
push 0x01
|
call foo(int, int)
|
|
__pascal
|
Parameter von links nach rechts auf den Stack - Funktion räumt den Stack auf
(wird nur vom BC-Compiler unterstützt)
|
__fastcall
|
Zunächst werden die Parameter in Registern vergeben - dann wird der Stack genutzt (funktioniert nicht unter BeOS)
int __fastcall foo(int a, int b) // Borland Example
|
{ return 3;
|
mov eax,0x00000003 ; return value
|
}
|
ret ; return
|
|
int c=foo(1,2); // Aufruf (Borland)
|
mov edx,0x00000002 ; last parameter
|
mov eax,0x00000001 ; first parameter
|
call foo(int,int)
|
|
|
|
int __fastcall foo(int a, int b) // Microsoft Example (optimized for Size)
|
push 3 ; return value to Stack
|
pop eax ; return value to eax
|
ret ; return
|
|
int c=foo(1,2); // Aufruf (Microsoft)
|
push 2 ; 2 to edx
|
pop edx
|
push 1 ; 1 to ecx
|
pop ecx
|
call foo
|
|
|
Rückgabewerte werden für:
8 Bit Werte |
im Register AL, |
16 Bit Werte |
im Register AX, |
32 Bit Werte |
im Register EAX |
zurückgegeben! (das ist für alle Windows-Compiler Standard!)
Unter Borland werden die Parameter die größer als 32
Bit sind vollständig auf dem Stack kopiert. Der Rückgabeparameter wird vom Aufrufer angelegt und dessen Adresse als letzter Parameter auf dem Stack hinterlegt.
struct4Bytes __stdcall foo(struct4Bytes a, struct4Bytes b) // Borland Example
|
push ebp ; save stackframe
|
mov ebp,esp ; new stackframe
|
add esp,-0x08 ; create variable c
|
mov eax,[ebp+0x08] ; ??
|
return c;
|
mov edx,[ebp-0x08] ; Copy variable c
|
mov [eax],edx ; to the return variable
|
mov edx,[ebp-0x04] ; which address was stored
|
mov [eax+0x04],edx ; on the stack
|
}
|
pop ecx ; delete variable c
|
pop ecx
|
pop ebp ; restore stackframe
|
ret 0x0014 ; return and delete stackparameters
|
|
struct4Bytes c=foo(a,b); // Aufruf
|
push [ebp+0xFFFFFF2C] ; store parameters on Stack
|
push [ebp+0xFFFFFF28] ;
|
push [ebp+0xFFFFFF34] ;
|
push [ebp+0xFFFFFF30] ;
|
lea eax,[ebp+0xFFFFFF20] ; save adress of the return value
|
push eax ; to the stack..
|
call foo(struct4Bytes,struct4Bytes)
|
|
Unter dem Microsoft-Compiler läuft das ein bischen anders. Ein 64-Bit oder 48-Bit grosser Rückgabewert wird im Registerpaar EAX:EDX
zurückgeliefert, alles größere wird in der aufgerufenen Funktion auf dem Stack aufgebaut und durch einem Pointer in EAX zurückgeliefert wo der Aufrufer das Objekt dann in eine eigene Variable kopieren kann.
struct256Bytes __stdcall foo(struct256Bytes a,struct256Bytes b) // Borland Example
|
{ struct256Bytes c;
|
push ebp ; Functionsprolog
|
mov ebp,esp ; Stackframe
|
sub esp,140h ; Variable c creieren
|
push ebx ; ebx sichern
|
push esi ; esi sichern
|
push edi ; edi sichern
|
return c;
|
mov ecx,40h ;
|
lea esi,[ebp-100h] ;
|
mov edi,dword ptr [ebp+8] ; ??
|
rep movs dword ptr [edi],dword ptr [esi] ; ?? mglw. debug - code?
|
mov eax,dword ptr [ebp+8] ; Adresse in eax
|
}
|
pop edi ; edi wiederherstellen
|
pop esi ; esi wiederherstellen
|
pop ebx ; ebx wiederherstellen
|
mov esp,ebp ; Stack korrigieren
|
pop ebp ; Stackframe restorieren
|
ret 204h ; return
|
|
struct256Bytes c=foo(a,b); // Aufruf
|
sub esp,100h
|
mov ecx,40h ; ??
|
lea esi,[ebp-200h] ; ?? debug code
|
mov edi,esp ; ??
|
rep movs dword ptr [edi],dword ptr [esi] ; b anlegen
|
sub esp,100h
|
mov ecx,40h ; ??
|
lea esi,[ebp-100h] ; ?? debug code
|
mov edi,esp ; ??
|
rep movs dword ptr [edi],dword ptr [esi] ; a anlegen
|
lea eax,[ebp-500h]
|
push eax ; Adresse des Ziel-Objektes in den Stack legen...
|
call foo ; Funktion aufrufen....
|
mov esi,eax
|
mov ecx,40h ; ?? value c
|
lea edi,[ebp-400h] ; ??
|
rep movs dword ptr [edi],dword ptr [esi] ; ?? debug code
|
mov ecx,40h
|
lea esi,[ebp-400h]
|
lea edi,[ebp-300h]
|
rep movs dword ptr [edi],dword ptr [esi] ; Zielvariable kopieren
|
|
Bei größeren Objekten ist wohl diesmal die Borland-Variante die cleverere, da
- die Funktion den Returnwert nicht mehr aufbauen muss (die Funktion kann entsprechend optimiert werden)
- der Returnwert einmal weniger kopiert werden muss
- der Stackframe übersichtlicher ist, und es kein (kurzzeitiges) Objekt gibt das ausserhalb des Stackframes existiert.
Nachdem ich herausgefunden hatte, das bei meinem Problem die Art
der Rückgabe der 64-Bit grossen Struktur POINT die entscheidende Rolle spielte konnte ich den Aufruf “wrappen” und mein Programm lief fehlerfrei.
Ich hoffe, meine Aussage über grosse Objekte ist
soweit richtig - einige sophisticated Assemblerschleifen habe ich nicht sehr tief analysiert - solltet Ihr Fehler finden - mailt mir einfach: christopher@kohlert.de.