首页  编辑  

调用约定

Tags: /超级猛料/Language.Object Pascal/内嵌汇编、函数、过程/   Date Created:

stdcall 从右到左,   被调用一方 (Win32标准)

cdcel   从右到左    调用程序

safecall 从右到左    被调用一方

fast-call 从左到右 使用寄存器(delphi缺省)

pascal   从左到右   被调用一方(win3.x缺省)

Register 调用约定

在Object Pascal 函数和过程中默认的调用约定是R e g i s t e r 。利用这种方法传递参数可以使代码更加

优化。因为编译器把头三个3 2 位参数分别用e a x 、e d x 和e c x 寄存器来传递。参看下面声明:

function Basm(i1,i2,i3:integer):integer;

可以认为参数I 1 的值被存储在e a x 中,I 2 在e d x 中,I3 在e c x 中。

再看另一个声明:

procedure TForm1.Basm(i1,i2:integer);

这里参数S 1 的值被存储在e a x 中,S 2 在e d x 中,隐含的s e l f 参数被存储在e c x 中。

Object/C++调用约定对应关系:

Object                          Pascal C + +

r e g i s t e r*                _ _ f a s t c a l l

p a s c a l                      _ _ p a s c a l

c d e c l                         _ _ c d e c l*

s t d c a l l                     _ _ s t d c a l l

注:有*的表示默认的调用约定。

************************

Calling conventions influence two things:

- how parameters are passed to a function/procedure (=routines)

- how the call stack is cleaned up when the call returns

Delphi routines can use the calling conventions pascal (the

Delphi 1 default), register (the default for Delphi 2-5), cdecl

(the default used by C/C++ compilers), stdcall (the default used

by the Windows API). There is a fifth one: safecall, which

is only used in the context of interface methods. A good

explanation for what it entails can be found in issue 51

(Nov. 99) of The Delphi Magazine, i will not go into it

further here. Lets go through the first four in detail, using a

couple of test functions with the same parameter list but

different calling conventions. For clearity we compile with

stack frames on, so each routine will start with the prologue

push ebp

mov ebp, esp

The stack layouts given below are for the mov line. Each test

function is called with the same parameter values so one can

use the CPU windows stack pane to study the resulting stack

layout.

1. Pascal calling convention

Function Test1( i: Integer; b: Boolean; d: Double ): Integer;

Pascal;

Pascal calling convention passes parameters on the stack and

pushes them from left to right in the parameter list. Each

parameter occupies a multiple of 4 bytes. The resulting stack

layout is

ebp + 20 value of i, 4 bytes

ebp + 16 value of b, 4 bytes, only lowbyte significant

ebp + 08 value of d, 8 bytes

ebp + 04 return address, 4 bytes

ebp + 00 old ebp value

The parameters are cleared off the stack by the called function

using a

ret $10

instruction ($10 = 16 is the total size of the parameters on

stack).

2. Register calling convention

Function Test2( i: Integer; b: Boolean; d: Double ): Integer;

Register;

Register calling convention passes parameters in registers

eax, edx, ecx and on the stack and processes them from left to

right in the parameter list. There are rules to decide what

goes into registers and what goes on the stack, as detailed

in the Object Pascal Language guide. The resulting stack layout

is

ebp + 08 value of d, 8 bytes

ebp + 04 return address, 4 bytes

ebp + 00 old ebp value

The value of i is passed in eax, the value of b in edx.

The parameters are cleared off the stack by the called function

using a

ret $8

instruction ($8 = 8 is the total size of the parameters on

stack).

3. cdecl calling convention

Function Test3( i: Integer; b: Boolean; d: Double ): Integer;

cdecl;

Cdecl calling convention passes parameters on the stack and

pushes them from right to left in the parameter list. Each

parameter occupies a multiple of 4 bytes. The resulting stack

layout is

ebp + 16 value of d, 8 bytes

ebp + 12 value of b, 4 bytes, only lowbyte significant

ebp + 08 value of i, 4 bytes

ebp + 04 return address, 4 bytes

ebp + 00 old ebp value

The parameters are cleared off the stack by the calling

function, so the function ends with a

ret 0

and after the call instruction we find a

add esp, $10

instruction ($10 = 16 is the total size of the parameters on

stack).

4. Stdcall calling convention

Function Test4( i: Integer; b: Boolean; d: Double ): Integer;

stdcall;

Sdtcall calling convention passes parameters on the stack and

pushes them from right to left in the parameter list. Each

parameter occupies a multiple of 4 bytes. The resulting stack

layout is

ebp + 16 value of d, 8 bytes

ebp + 12 value of b, 4 bytes, only lowbyte significant

ebp + 08 value of i, 4 bytes

ebp + 04 return address, 4 bytes

ebp + 00 old ebp value

The parameters are cleared off the stack by the called function

using a

ret $10

instruction ($10 = 16 is the total size of the parameters on

stack).

When writing DLLs that are only be meant to be used from Delphi

programs you will usually use the register calling convention,

since it is the most efficient one. But this really ties the

DLL to Delphi, no program compiled in another language (with

the exception of BC++ Builder perhaps) will be able to use the

DLL unless it uses assembler to call the functions, since the

Register calling convention (like MS VC _fastcall) is

compiler-specific.

When writing DLLs that should be usable by other programs

regardless of language you use the stdcall calling convention

for exported routines. Any language that can call Windows API

routines will be able to call routines from such a DLL, as long

as you stay away from Delphi-specific data types, like String,

Boolean, objects, real48.

Pascal calling convention is Win16 heritage, it was the default

for the Win16 API but is no longer used on Win32.

A topic loosely tied to calling conventions is name decoration

for exported names in DLLs. Delphi (5 at least) does not

decorate names, regardless of calling convention used. The name

appears in the exported names table exactly as you cite it in

the exports clause of the DLL, case and all. Case is

significant for exported functions in Win32!

Other compilers may decorate names. Unless told to do otherwise

a C compiler will prefix all cdecl functions with an underbar

and will decorate stdcall functions in the format _name@x,

where x is the total parameter size, e.g. _Test3@16. C++ is

even worse, unless functions are declared as extern "C" it will

export names in a decorated format that encodes parameter size

and type, in a compiler-specific fashion. For routines exported

with Pascal calling convention the names may be all uppercase,

but as said above you will not usually encouter this convention

on Win32.

Due to these naming issues it is often appropriate to sic TDUMP

on an unknown DLL you want to interface to, to figure out the

actual names of the exported functions. These can then be given

in a name clause for the external statement if they are

decorated.

Demo DLL:

library DemoDLL;

uses

 Windows;

function Test1(i: Integer; b: Boolean; d: Double): Integer; pascal;

begin

 Result := Round(i * Ord(b) * d);

end;

function Test2(i: Integer; b: Boolean; d: Double): Integer; register;

begin

 Result := Round(i * Ord(b) * d);

end;

function Test3(i: Integer; b: Boolean; d: Double): Integer; cdecl;

begin

 Result := Round(i * Ord(b) * d);

end;

function Test4(i: Integer; b: Boolean; d: Double): Integer; stdcall;

begin

 Result := Round(i * Ord(b) * d);

end;

exports

 Test1 index 1,

 Test2 index 2,

 Test3 index 3,

 Test4 index 4;

begin

end.

// Example call from test project:

implementation

{$R *.DFM}

function Test1(i: Integer; b: Boolean; d: Double): Integer;

 pascal; external 'DEMODLL.DLL' Index 1;

function Test2(i: Integer; b: Boolean; d: Double): Integer;

 register; external 'DEMODLL.DLL' Index 2;

function Test3(i: Integer; b: Boolean; d: Double): Integer;

 cdecl; external 'DEMODLL.DLL' Index 3;

function Test4(i: Integer; b: Boolean; d: Double): Integer;

 stdcall; external 'DEMODLL.DLL' Index 4;

procedure TForm1.Button1Click(Sender: TObject);

var

 i: Integer;

begin

 i := Test1(16, True, 1.0);

 i := Test2(16, True, 1.0);

 i := Test3(16, True, 1.0);

 i := Test4(16, True, 1.0);

end;

Set breakpoints on the lines and step into the routines with the

CPU window open to see the stack layout.