Delphi と Windows の呼び出し規約

# はじめに

Delphi の Windows ターゲットプラットフォームでの呼び出し規約を調べてみました。

# 呼び出し規約 (Calling Conventions)

Delphi (Win32) で使える呼び出し規約は次の通りです。デフォルトは **register** 呼び出し規約です。

| 指令 | パラメータの<br>順序 (方向) | クリーンアップの<br>担当 | レジスタ経由の<br>パラメータ渡し |
|:---|:---|:---|:---:|
|**register**|左から右 |ルーチン |○|
|**pascal**|左から右|ルーチン|×|
|**cdecl**|右から左|呼び出し側|×|
|**stdcall**|右から左|ルーチン|×|
|**safecall**|右から左|ルーチン|×|

16bit Delphi (**Delphi 1**) には **pascal** と **cdecl** の他に次のような指令がありましたが、32bit / 64bit コンパイラでは無視されます。

| 指令 | 意味|
|:---|:---|
|**near**|near 呼び出しモデルを指定する。プログラムで宣言されたルーチンや、ユニットの implementation セクションで宣言されたルーチンは暗黙的に near となる。far 呼び出しモデルよりもやや高速に動作する。|
|**far**|far 呼び出しモデルを指定し、他のモジュール (ユニット) からの呼び出しを可能とする。ユニットの interface セクションで宣言されたルーチンは暗黙的に far となる。手続き型 (関数ポインタ) を使うルーチンは far 呼び出しモデルでなくてはならない。|
|**export**| DLL でエクスポートされるルーチンやコールバックルーチンに指定する必要がある。far 呼び出しモデルを強制する。|

少し話は逸れますが、**Delphi 1** には **interrupt** 指令もありました。これは割り込み処理用なのですが、**Delphi 1** で実際に使えたかどうかは確認していません。次のような `割り込み手続き` で利用されました。

```pascal
procedure IntHandler(Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP: word); interrupt;
begin
  ...
end;
```

**interrupt** 指令は **Delphi 2** 以降でエラーになります。
 
## 呼び出し規約の違いを確認する

呼び出し規約の違いを確認してみましょう。まずは次のような **Windows 32 bit コンソールアプリケーション**を用意します。

```pascal:CallingConversionTest.dpr
program CallingConversionTest;

{$APPTYPE CONSOLE}

function Calc(a: Integer; b: Integer; c: Integer): Integer;
begin
  Result := a + b + c;
end;

var
  v: Integer;
begin
  v := Calc(1, 2, 3);
  Writeln(v);
end.
```

`v := Calc(1, 2, 3);` の行にブレークポイントを置き、デバッグ実行します。

ブレークポイントで止まったら `〔Ctrl〕+〔Alt〕+〔D〕` を押して逆アセンブルリストを出します。メインメニューから [表示 | デバッグ | CPU ウィンドウ | 逆アセンブル] で出す事もできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/dc59f275-195e-e66e-6e4d-58c692ce917d.png)
**See also:**

 - [【Delphi】CPU ウィンドウを Delphi 2 および 3 で使う](https://qiita.com/ht_deko/items/56fd79678e418e922e4a)


## ■ register 呼び出し規約

```pascal
function Calc(a: Integer; b: Integer; c: Integer): Integer; register;
```

ルーチンの末尾に **register** を指定するか何も指定しなければ **register 呼び出し** です。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3);
0040B100 B903000000       mov ecx,$00000003
0040B105 BA02000000       mov edx,$00000002
0040B10A B801000000       mov eax,$00000001
0040B10F E8C4ECFFFF       call Calc
0040B114 A388054100       mov [$00410588],eax
```

引数が ecx, edx, eax レジスタに格納されています。パラメータが 4 個以上だとそれらは左から順にスタックに積まれます。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3, 4, 5);
0040B100 6A04             push $04
0040B102 6A05             push $05
0040B104 B903000000       mov ecx,$00000003
0040B109 BA02000000       mov edx,$00000002
0040B10E B801000000       mov eax,$00000001
0040B113 E8C0ECFFFF       call Calc
0040B118 A388054100       mov [$00410588],eax
```

| パラメータ | レジスタ/<br>スタック |
|:-:|:-:|
| 1 | eax |
| 2 | edx |
| 3 | ecx  |
| 4 | スタック #1 |
| 5 | スタック #2 |

## ■ pascal 呼び出し規約

```pascal
function Calc(a: Integer; b: Integer; c: Integer): Integer; pascal;
```

下位互換性のために残されている呼び出し規約です。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3);
0040B100 6A01             push $01
0040B102 6A02             push $02
0040B104 6A03             push $03
0040B106 E8CDECFFFF       call Calc
0040B10B A388054100       mov [$00410588],eax
```

| パラメータ | レジスタ/<br>スタック |
|:-:|:-:|
| 1 | スタック #1 |
| 2 | スタック #2 |
| 3 | スタック #3 |


引数が左から順にスタックに積まれます。ドキュメントに

> register 呼び出し規約と pascal 呼び出し規約の場合、評価順序は定義されていません。

と書いてありますが、ここで言う `評価順序` はスタックに積まれる順序 (方向) の事ではありません。 

16bit の Windows API (Win16 API) は **(far) pascal** 呼び出し規約 でした。次のコードは Windows 3.x の `WINDOWS.H` と Windows 95 の `WINDEF.H` からの抜粋です。

```cpp:WINDOWS.H（Windows3.0）
...
#define FAR                 far
#define NEAR                near
#define PASCAL              pascal

#define API                 far pascal
#define CALLBACK            far pascal
...
BOOL  API PostMessage(HWND, WORD, WORD, WORD);
LONG  API SendMessage(HWND, WORD, WORD, WORD);
...

```

```cpp:WINDOWS.H（Windows3.1）
...
#define FAR                 _far
#define NEAR                _near
#define PASCAL              _pascal
#define CDECL               _cdecl

#define WINAPI              _far _pascal
#define CALLBACK            _far _pascal
...
typedef UINT WPARAM;
typedef LONG LPARAM;
typedef LONG LRESULT;
...
BOOL    WINAPI PostMessage(HWND, UINT, WPARAM, LPARAM);
LRESULT WINAPI SendMessage(HWND, UINT, WPARAM, LPARAM);
...
```

```cpp:WINDEF.H（Windows95）
#if (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define WINAPIV     __cdecl
#define APIENTRY    WINAPI
#define APIPRIVATE  __stdcall
#define PASCAL      __stdcall
#else
#define CALLBACK
#define WINAPI
#define WINAPIV
#define APIENTRY    WINAPI
#define APIPRIVATE
#define PASCAL      pascal
#endif
```

古い MacOS のライブラリ (68K / PowerPC) も **pascal** 呼び出し規約 でした。次の文は Apple の『Building and Managing Programs in MPW (Second Edition)』からの引用です。

>**Remove the pascal Keyword**
>In the PowerPC runtime environment, Pascal and C calling conventions are identical. If you declare a function with the pascal keyword, the PowerPC compiler uses the same calling convention as if you had declared it without the pascal keyword. In PowerPC compilers, function parameters are pushed onto the stack from left to right.


## ■ cdecl 呼び出し規約

```pascal
function Calc(a: Integer; b: Integer; c: Integer): Integer; cdecl;
```

C / C++ のデフォルトの呼び出し規約です。Windows を除く、多くの OS のライブラリはこの **cdecl** 呼び出し規約 です。引数は右から順にスタックに積まれます。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3);
0040B100 6A03             push $03
0040B102 6A02             push $02
0040B104 6A01             push $01
0040B106 E8CDECFFFF       call Calc
0040B10B 83C40C           add esp,$0c
0040B10E A388054100       mov [$00410588],eax
```

| パラメータ | レジスタ/<br>スタック |
|:-:|:-:|
| 1 | スタック #3 |
| 2 | スタック #2 |
| 3 | スタック #1 |

**cdecl** 呼び出し規約 ではスタックポインタ (ESP) の後始末を呼び出し側で行う必要があります。パラメータが 5 つになるとこのようなコードになります。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3, 4, 5);
0040B100 6A05             push $05
0040B102 6A04             push $04
0040B104 6A03             push $03
0040B106 6A02             push $02
0040B108 6A01             push $01
0040B10A E8C9ECFFFF       call Calc
0040B10F 83C414           add esp,$14
0040B112 A388054100       mov [$00410588],eax
```

## ■ stdcall 呼び出し規約

```pascal
function Calc(a: Integer; b: Integer; c: Integer): Integer; stdcall;
```

32bit の Windows API (Win32 API) の殆どがこの **stdcall** 呼び出し規約 です。つまり Windows NT 3.1 から Windows API は **stdcall** 呼び出し規約になった事になります。

言語を問わず、DLL で外部公開するルーチンは **stdcall** にしておけばトラブルも少ないと思います。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3);
0040B100 6A03             push $03
0040B102 6A02             push $02
0040B104 6A01             push $01
0040B106 E8CDECFFFF       call Calc
0040B10B A388054100       mov [$00410588],eax
```

**pascal** 呼び出し規約の逆で、引数は右から順にスタックに積まれます。

| パラメータ | レジスタ/<br>スタック |
|:-:|:-:|
| 1 | スタック #3 |
| 2 | スタック #2 |
| 3 | スタック #1 |

**winapi** キーワードを用いると、ターゲットプラットフォームのデフォルトの呼び出し規約となるため、32bit Windows 環境では **stdcall** 呼び出し規約と同等になります。

## ■ safecall 呼び出し規約

```pascal
function Calc(a: Integer; b: Integer; c: Integer): Integer; safecall;
```

例外ファイアウォールを持つ呼び出し規約です。COM で使われます。**stdcall** 呼び出し規約 とほぼ同じです。

```
CallingConversionTest.dpr.14: v := Calc(1, 2, 3);
0040B100 8D45EC           lea eax,[ebp-$14]
0040B103 50               push eax
0040B104 6A03             push $03
0040B106 6A02             push $02
0040B108 6A01             push $01
0040B10A E8C1EDFFFF       call Calc
0040B10F E8D4DDFFFF       call @CheckAutoResult
0040B114 8B45EC           mov eax,[ebp-$14]
0040B117 A388054100       mov [$00410588],eax
```

上記コードはテストとしては適当ではありません。吐かれるアセンブリコードがどういうものになるかの検証である事に留意してください。

| パラメータ | レジスタ/<br>スタック |
|:-:|:-:|
| 1 | スタック #3 |
| 2 | スタック #2 |
| 3 | スタック #1 |

**safecall 呼び出し** の関数は、

```pascal
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
```

暗黙的に HRESULT 型の結果 (戻り値) を持っています。

```pascal
function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
```

**See also:**

 - [<11> 手続きと関数 (標準 Pascal 範囲内での Delphi 入門) (Qiita)](https://qiita.com/ht_deko/items/b93ac03bfee002f17137)

## C++Builder との互換性

C++Builder を使って C++ との互換性を確認してみます。C++Builder 用に次のようなコンソールアプリケーションを用意します。

```cpp:File1.cpp
#pragma hdrstop
#pragma argsused

#ifdef _WIN32
#include <tchar.h>
#else
  typedef char _TCHAR;
  #define _tmain main
#endif

#include <stdio.h>

int Calc(int a, int b, int c, int d, int e) {
	return a + b + c + d + e;
}

int _tmain(int argc, _TCHAR* argv[])
{
	int v = Calc(1, 2, 3, 4, 5);
	printf("%d\n", v);

	return 0;
}
```

これを C++Builder の古いコンパイラ (BCC32) でコンパイルします。Clang ベースの BCC32C/X ではなく BCC32 を使うのは、吐かれるアセンブリコードが Delphi のものに似てシンプルだからです。

`int v = Calc(1, 2, 3, 4, 5);` の行にブレークポイントを置き、デバッグ実行します。ブレークポイントで止まったら、Delphi の時と同じように `〔Ctrl〕+〔Alt〕+〔D〕` を押して逆アセンブルリストを出します。


### ■ cdecl 呼び出し規約 (cdecl、\_cdecl、__cdecl)

```cpp
int __cdecl Calc(int a, int b, int c, int d, int e)
```

何も指定しなかった場合や `__cdecl` を指定した場合には、**cdecl** 呼び出し規約 でした。

```
File1.cpp.19: int v = Calc(1, 2, 3, 4, 5);
00401214 6A05             push $05
00401216 6A04             push $04
00401218 6A03             push $03
0040121A 6A02             push $02
0040121C 6A01             push $01
0040121E E8D9FFFFFF       call Calc(int,int,int,int,int)
00401223 83C414           add esp,$14
00401226 8945FC           mov [ebp-$04],eax
```

### ■ pascal 呼び出し規約 (pascal、\_pascal、__pascal)

```cpp
int __pascal Calc(int a, int b, int c, int d, int e)
```

`__pascal` を指定した場合には、**pascal** 呼び出し規約 でした。

```
File1.cpp.19: int v = Calc(1, 2, 3, 4, 5);
00401218 6A01             push $01
0040121A 6A02             push $02
0040121C 6A03             push $03
0040121E 6A04             push $04
00401220 6A05             push $05
00401222 E8D5FFFFFF       call Calc(int,int,int,int,int)
00401227 8945FC           mov [ebp-$04],eax
```

Win16 API は **(far) pascal** 呼び出し、Win32 API は **stdcall** 呼び出しだったので、他の C/C++ コンパイラでは便宜上次のような定義をしている事があります。

```cpp
#define PASCAL      __stdcall
```

Win32 用の定義だけを見て**「pascal 呼び出し規約って stdcall 呼び出し規約と同じなんだな！」**と思ってはいけません。

|  | Win16 | Win32 |
|:---|:---:|:---:|
| Borland の **pascal** 呼び出し規約 | スタック (左->右)<br>**(pascal)** | スタック (左->右)<br>**(pascal)** |
| 他メーカー の **pascal** 呼び出し規約 | スタック (左->右)<br>**(pascal)** | スタック (右->左)<br>**(stdcall)** |

「互換性をどう取ったか」によって実装が異なります。Pascal 系の言語を自社ラインナップに持っていれば、Borland のような対応が自然だったかと思います。

### ■ stdcall 呼び出し規約 (\_stdcall、__stdcall)

```cpp
int __stdcall Calc(int a, int b, int c, int d, int e)
```

`__stdcall` を指定した場合には、**stdcall** 呼び出し規約 でした。

```
File1.cpp.19: int v = Calc(1, 2, 3, 4, 5);
00401218 6A05             push $05
0040121A 6A04             push $04
0040121C 6A03             push $03
0040121E 6A02             push $02
00401220 6A01             push $01
00401222 E8D5FFFFFF       call Calc(int,int,int,int,int)
00401227 8945FC           mov [ebp-$04],eax
```

### ■ Borland fastcall 呼び出し規約 (\_fastcall、__fastcall)

```cpp
int __fastcall Calc(int a, int b, int c, int d, int e)
```

`__fastcall` を指定した場合には、**register** 呼び出し規約 でした。

```
File1.cpp.20: printf("%d\n", v);
00401224 6A04             push $04
00401226 6A05             push $05
00401228 B903000000       mov ecx,$00000003
0040122D BA02000000       mov edx,$00000002
00401232 B801000000       mov eax,$00000001
00401237 E8C0FFFFFF       call Calc(int,int,int,int,int)
0040123C 8945FC           mov [ebp-$04],eax
```

C++Builder で VCL / Firemonkey アプリケーションを作ると **__fastcall** があちこちに出てくるのはこれが理由です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/af4390f1-1b18-edca-41db-d1d51b49c78e.png)

### ■ Microsoft fastcall 呼び出し規約 (\_msfastcall、__msfastcall)

```cpp
int __msfastcall Calc(int a, int b, int c, int d, int e)
```

`__msfastcall` を指定した場合には、Microsoft 互換の **fastcall** 呼び出し規約 となりました。この呼び出し規約は Delphi には存在しません。

```
File1.cpp.19: int v = Calc(1, 2, 3, 4, 5);
00401224 6A05             push $05
00401226 6A04             push $04
00401228 6A03             push $03
0040122A BA02000000       mov edx,$00000002
0040122F B901000000       mov ecx,$00000001
00401234 E8C3FFFFFF       call Calc(int,int,int,int,int)
00401239 8945FC           mov [ebp-$04],eax
```

引数が edx, ecx レジスタに格納されています。パラメータが 3 個以上だとそれらは右から順にスタックに積まれます。

| パラメータ | レジスタ/<br>スタック |
|:-:|:-:|
| 1 | ecx |
| 2 | edx |
| 3 | スタック #3 |
| 4 | スタック #2 |
| 5 | スタック #1 |

BCC32 のコンパイラオプションには `_fastcall` を Microsoft の **fastcall** 呼び出し規約 とみなすオプションがあります。

## 64bit Windows の場合

Windows 64bit プラットフォームの場合、Delphi では **safecall** 以外の指令を無視し、呼び出しに **Microsoft x64** 呼び出し規約 が使われます。

```
CallingConversionTest.dpr.13: v := Calc(1, 2, 3, 4, 5);
000000000040EA4F B901000000       mov ecx,$00000001
000000000040EA54 BA02000000       mov edx,$00000002
000000000040EA59 41B803000000     mov r8d,$00000003
000000000040EA5F 41B904000000     mov r9d,$00000004
000000000040EA65 C744242005000000 mov [rsp+$20],$00000005
000000000040EA6D E86EFFFFFF       call Calc
000000000040EA72 890568A70000     mov [rel $0000a768],eax
```

**Microsoft x64** 呼び出し規約は **64bit 版の (ms)fastcall** 呼び出し規約とでも言うべきもので、最初の 4 つはレジスタに格納され、残りは右から順にスタックに積まれます (実際にはもう少し複雑です)。

これにより、**pascal** 呼び出し規約も次のようになりました。

|  | Win16 | Win32 | Win64 |
|:---|:---:|:---:|:---:|
| Embarcadero の **pascal 呼び出し規約** | **pascal** | **pascal (32)** | **Microsoft x64** |
| 他メーカー の **pascal 呼び出し規約** | **pascal** | **stdcall** | **Microsoft x64** |
| **WINAPI** キーワード | **far pascal** [^1] | **stdcall** | **Microsoft x64** |

やろうと思えば `スタック (左->右)` な 64bit の **pascal** 呼び出し規約も実装できたと思いますが、Win32 の時点でも **pascal** 呼び出し規約は (ほぼ) 使われていなかったので、もはやそうするメリットはなかったのだろうと思われます。

**safecall** 呼び出し規約もレジスタとスタックの使い方は同じなのですが、HRESULT があるのでちょっと違うアセンブリコードが吐かれます。

```
CallingConversionTest.dpr.13: v := Calc(1, 2, 3, 4, 5);
000000000040EABF B901000000       mov ecx,$00000001
000000000040EAC4 BA02000000       mov edx,$00000002
000000000040EAC9 41B803000000     mov r8d,$00000003
000000000040EACF 41B904000000     mov r9d,$00000004
000000000040EAD5 C744242005000000 mov [rsp+$20],$00000005
000000000040EADD 488D4538         lea rax,[rbp+$38]
000000000040EAE1 4889442428       mov [rsp+$28],rax
000000000040EAE6 E835FFFFFF       call Calc
000000000040EAEB 89C1             mov ecx,eax
000000000040EAED E88EE1FFFF       call @CheckAutoResult
000000000040EAF2 8B4538           mov eax,[rbp+$38]
000000000040EAF5 8905EDA60000     mov [rel $0000a6ed],eax
```

こちらもテストとしては適当でありません。吐かれるアセンブリコードがどういうものになるかの検証である事に留意してください。


# おわりに

16bit / 32bit Windows プログラミングでは、呼び出し規約に注意する必要があります。

64bit Windows プログラミングではあまり気にする必要はなさそうですが、32bit / 64bit 共用のコードを書く際には呼び出し規約に注意しないといけませんね。

**See also:**

 - [呼び出し規約 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%EF%BC%88Delphi%EF%BC%89#.E5.91.BC.E3.81.B3.E5.87.BA.E3.81.97.E8.A6.8F.E7.B4.84)
 - [言語混在時の呼び出し規約 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E8%A8%80%E8%AA%9E%E6%B7%B7%E5%9C%A8%E6%99%82%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E8%A6%8F%E7%B4%84)
 - [呼出規約 (Wikipedia)](https://ja.wikipedia.org/wiki/%E5%91%BC%E5%87%BA%E8%A6%8F%E7%B4%84)
 - [Windowsにおける呼出規約の歴史 (Owl's perspective)](http://owlsperspective.blogspot.com/2011/02/history-of-calling-conventions.html)
 - [x64 での呼び出し規則 (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/cpp/build/x64-calling-convention)
 - [x86 calling conventions (Wikipedia: en)](https://en.wikipedia.org/wiki/X86_calling_conventions)

[^1]: Windows 3.0 では "**API**" キーワード (それより前の Windows では未確認) が `far pascal` で定義されていました。