Evolution Programming Resources

  Projects

   Calling Conventions
unter Windows


1. Notwendigkeit

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...

2. Was sind Calling Conventions

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!)

3. Größere Parameter

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.

4. Fazit

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.


      Top 
 

| © 2000 by 3rd-evolution