#はじめに
DLL関数の呼び出し規約は「__stdcall
」か「__cdecl
」のどちらかになるが
一般的には__stdcall
で定義される事が多いようである。
少なくとも私は今まで__cdecl
で定義されたDLLを見たことがない。
両者の違いは、スタック(引数、戻り値)の処理を
「exe側(呼び出す側)」がやるか「dll側(呼び出される側)」がやるか、だったと思う
(知らない、もっと詳しく知りたという人は各自ググってね)
この呼び出し規約、単一プロジェクトでは全く問題にならないのだが1
DLL化すると、不整合を起こした場合とんでもない問題を引き起こす
例えば__cdecl
で作られたDLLを__stdcall
として呼び出すと、面白いようにバグる。
しかもコレ、システムが検知不可なので問題に気付きにくいというオマケ付きである。
(ローカル変数の値が突如書き換えられるとか、そんな感じの動作になる)
#お作法
VisualStudioで開発する場合において、呼び出し規約を定義する方法は2つある。
- 関数定義に「
__stdcall
」「__cdecl
」等と明示する - プロジェクト設定から「規定の呼び出し規約」を変更する
結論から書くと、手法2は注意が必要である。出来れば避けたい。
##手法1について
恐らく殆どの参考書がこの方式を推奨しているし、誰だってそうする。
ソースコードにベタ書きするため、コンパイラオプションの設定に依存せず書いた通りに動く。
ただ、exportする関数全てに「__stdcall
」だの「WINAPI
」だの書くのは面倒だと感じる。
さらに付け足すと、記述の約束事で
int __stdcall piyo();
と書くことになるが、これを並べ替えて
__stdcall int piyo();
と書くとエラーを吐く、というのも面倒である
(よく使われる手法 #define _EXPORT __declspec(dllexport)
で一緒に処理できない)
##手法2について
実際楽、プロジェクト内の全関数が一括で設定されるので漏れが発生しない。
(ローカル関数が__stdcallだろうが__cdeclだろうが大差ないし)
#起こりうる問題とは
手法2で作成した関数定義ヘッダーを、他のプロジェクトで読み込むと
呼び出し規約の不一致が発生する可能性がある。
Project_A(DLL側)では、関数は「__stdcall
」として認識されるが
Project_B(EXE側)では、関数は「__cdecl
」として認識される恐れがある
(プロジェクトの設定に依存する。なお、VisualStudioのデフォルト値は__cdecl
である)
考えてみれば当然だが、実際に問題が起きるまでは意外と気付かないものである。
#言い訳
アプリはC#、処理はC++で書いてDLLにするという開発手法に慣れてると
意外と気付かないんですよ、C#の[DllImport]
ってデフォルトで__stdcall
扱いしてくれるし
そもそもexe側で*.h
ファイル使わないし
(そして、たまにC++アプリを書く時に罠にハマる)
#まとめ
面倒でも、DLL関数は呼び出し規約を明示しましょう。
-
コンパイラ自身が把握している、という意味で ↩