Windowsでは__cdeclまたは__stdcall?


84

私は現在、DLLとして配布されるWindows用のC ++ライブラリを開発しています。私の目標は、バイナリの相互運用性を最大化することです。より正確には、DLLの関数は、DLLを再コンパイルしなくても、MSVC ++とMinGWの複数のバージョンでコンパイルされたコードから使用できる必要があります。ただし、どの呼び出し規約が最適cdeclか、またはについて混乱していstdcallます。

「コンパイラ間で同じであることが保証されているのはC呼び出し規約だけです」などのステートメントを聞くことがありますcdeclこれは、の解釈には、特に値を返す方法にいくつかのバリエーションがあります」などのステートメントとは対照的です。これは、特定のライブラリ開発者(libsndfileなど)が配布するDLLでC呼び出し規約を使用することを、目に見える問題なしに阻止するようには見えません。

一方、stdcall呼び出し規約は明確に定義されているようです。私が聞いたところによると、これはWin32とCOMで使用される規則であるため、すべてのWindowsコンパイラは基本的にそれに従う必要があります。これは、Win32 / COMをサポートしていないWindowsコンパイラはあまり役に立たないという仮定に基づいています。フォーラムに投稿された多くのコードスニペットは関数を宣言していますがstdcall理由を明確に説明する単一の投稿を見つけることができないようです。

矛盾する情報が多すぎて、実行する検索ごとに異なる回答が得られるため、2つを判断するのに役立ちません。なぜどちらかを選択する必要があるのか​​(または2つが同等である理由)について、明確で詳細な議論のある説明を探しています。

ほとんどのクライアントコードは、「インターフェース」、純粋仮想クラス(次のパターンは、例えば説明を通じて、私のDLLとのインタフェースになるので、この質問は、「古典的な」機能に、だけでなく、仮想メンバ関数の呼び出しに適用されるだけでなく、注意ここそこ)。


4
「cdeclの解釈には、特に値を返す方法にいくつかのバリエーションがあります」-これは、x86でcdeclを使用する異なるOSがcdeclの異なるバリアントを使用する可能性があることを意味します。つまり、両方が「cdecl」と呼ぶ異なるABIです。Windowsはバリエーションを特定し、Windowsで実行され、Windowsの選択を尊重しない実装は、Windows cdecl関数を呼び出すことができないため、stdcallで言うように、Windowsプログラミングには役に立たず、いくつかの標準Cがあります。実装が難しい機能。
スティーブジェソップ2011年

1
__stdcall呼び出し規約は、Win32API関数を呼び出すために使用されます。docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Zanna

回答:


79

私は実際のテストをいくつか行いました(DLLとアプリケーションをMSVC ++とMinGWでコンパイルしてから、それらを混合します)。見たところ、cdecl呼び出し規約の方が良い結果が得られました。

具体的には、の問題stdcallは、を使用している場合でも、MSVC ++がDLLエクスポートテーブルの名前をマングルすることextern "C"です。たとえば、にfooなり_foo@4ます。これは__declspec(dllexport)、DEFファイルを使用しているときではなく、を使用しているときにのみ発生します。ただし、DEFファイルはメンテナンスの手間がかかると思いますので、使いたくありません。

MSVC ++の名前マングリングには、次の2つの問題があります。

  • GetProcAddressDLLでの使用は少し複雑になります。
  • MinGWはデフォルトでは、装飾された名前の前にアンスコアを付けません(たとえば、MinGWはfoo@4代わりにを使用します_foo@4)。これにより、リンクが複雑になります。また、「アンダースコアバージョン」と互換性のないDLLやアプリケーションの「非アンダースコアバージョン」が実際にポップアップするリスクがあります。

私はこのcdecl規則を試しました。MSVC++とMinGWの間の相互運用性は完全に機能し、すぐに使用でき、名前はDLLエクスポートテーブルで装飾されません。仮想メソッドでも機能します。

これらの理由から、cdeclは私にとって明らかな勝者です。


特に、これは64ビットDLLには適用されません。これは、__ stdcallと__cdeclの両方が通常無視されるため、エクスポートされたC関数で名前マングリングが発生しないためです。
jtbr

@jtbrエクスポートされたC ++関数(つまりno extern "C")はどうですか?
ela782 2018年

@ Ela782 C ++には、関数のオーバーロードをサポートするために、常に名前マングリングがあります。ただし、これは標準化されていないため、コンパイラによって異なる可能性があります。
jtbr

@jtbr申し訳ありませんが、名前がマングリングするという意味ではありませんでした。エクスポートされたC ++関数を使用する64ビットDLLでも、__ stdcallと__cdeclの両方が無視されるかどうかを意味しました。
ela782 2018年

1
@ Ela782はい; __stdcallと__cdeclは、すべてのx86-64コンパイルで無視されると思います。ウィキペディアを参照してください。
jtbr

20

2つの呼び出し規約の最大の違いは、「_ _cdecl」は、呼び出し元での関数呼び出しの後にスタックのバランスを取る負担をかけることです。これにより、引数の量が可変の関数が可能になります。「__stdcall」規則は本質的に「単純」ですが、この点では柔軟性が低くなります。

また、マネージド言語はデフォルトでstdcall規則を使用していると思います。そのため、cdeclを使用する場合は、P / Invokeを使用する人は呼び出し規約を明示的に指定する必要があります。

したがって、すべての関数シグネチャが静的に定義される場合、cdeclでなくても、おそらくstdcallに傾倒します。


11

セキュリティの観点__cdeclから、スタックの割り当てを解除する必要があるのは呼び出し元であるため、規則は「より安全」です。__stdcallライブラリで発生する可能性があるのは、開発者がスタックの割り当てを適切に解除するのを忘れたか、攻撃者がDLLのスタックを破損して(APIフックなどによって)コードを挿入し、呼び出し元がチェックしない可能性があることです。私の直感が正しいことを示すCVSセキュリティの例はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.