4

私のソリューションには、関数をエクスポートするアンマネージド C++ DLL と、この関数を PInvoke するマネージド アプリケーションがあります。

ソリューションを .NET 3.5 から .NET 4.0 に変換したところ、この PInvokeStackImbalance "A call to PInvoke function [...] has unbalanced the stack"例外が発生しました。結局のところ、__stdcall だったので、__cdecl された関数を呼び出していました。

C++ 部分 (呼び出し先):

__declspec(dllexport) double TestFunction(int param1, int param2); // by default is __cdecl

C# 部分 (呼び出し元):

[DllImport("TestLib.dll")] // by default is CallingConvention.StdCall
private static extern double TestFunction(int param1, int param2);

それで、私はバグを修正しましたが、これが .NET 3.5 でどのように機能したのかに興味がありますか? (何度も繰り返される) 誰も (呼び出し先も呼び出し元も) スタックをクリーンアップせず、スタック オーバーフローやその他の不正行為を引き起こさず、正常に機能したのはなぜですか? Raymond Chen の記事で言及されているように、PInvoke には何らかのチェックがありますか? また興味深いのは、反対のタイプの規約違反 (__stdcall 呼び出し先を __cdecl のように PInvoked にする) がまったく機能せず、単に EntryPointNotFoundException が発生する理由です。

4

4 に答える 4

7

PInvokeStackImbalanceも例外ではありません。これはMDA警告であり、ManagedDebuggingAssistantによって実装されます。そのMDAをアクティブにすることはオプションであり、[デバッグ+例外]ダイアログから構成できます。デバッガーなしで実行すると、アクティブになることはありません。

スタックのバランスが崩れると、奇妙なデータ破損からSOEまたはAVEの取得に至るまで、かなり厄介な問題が発生する可能性があります。診断も非常に難しい。ただし、問題が発生することもありません。メソッドが戻ると、スタックポインタが復元されます。

64ビットにコンパイルされたコードは復元力がある傾向があり、関数の引数の多くがスタックではなくレジスタを介して渡されます。VS2010の新しいデフォルトであるx86で強制的に実行すると、失敗します。

于 2011-02-17T22:02:31.810 に答える
5

いくつかの調査の後:

状況をクラッシュから救うヘルパーは、別のレジスタ - EBP、スタック フレームの先頭を指すベース ポインタです。関数のローカル変数へのすべてのアクセスは、このポインターを介して行われます (最適化されたコードを除き、以下の編集を参照してください)。関数が戻る前に、スタック ポインターはベース ポインターの値にリセットされます。

関数 (PInvoke など) が別の関数 (インポートされた DLL の関数) を呼び出す前に、スタック ポインターは呼び出し元関数のローカル変数の末尾を指します。次に、呼び出し元はパラメーターをスタックにプッシュし、その他の関数を呼び出します。

上記の状況では、関数が別の関数を __stdcall として呼び出す場合、実際には __cdecl ですが、これらのパラメーターからスタックをクリアする人はいません。そのため、呼び出し先から戻った後、スタック ポインターはプッシュされたパラメーター ブロックの末尾を指します。これは、呼び出し元関数 (PInvoke) がさらにいくつかのローカル変数を取得したようなものです。

呼び出し元のローカル変数へのアクセスはベース ポインターを介して行われるため、何も壊れません。発生する可能性がある唯一の悪いことは、呼び出し先関数が一度に何度も呼び出される場合です。この場合、スタックが大きくなり、オーバーフローする可能性があります。しかし、PInvoke は DLL の関数を 1 回だけ呼び出してから戻るため、スタック ポインターはベース ポインターにリセットされるだけで、すべて問題ありません。 編集: hereに記載されているように、ローカル変数を CPU レジスタのみに格納するようにコードを最適化することもできます。この場合、EBP は使用されないため、無効な ESP によって無効なアドレスに戻る可能性があります。

于 2011-02-18T17:07:07.380 に答える
-1

を使用するDllImport場合、デフォルトは実際のものWinApiであり、ではありませんStdCall。WinApiは実際には規則ではありませんが、システムのデフォルトの規則を表しています。おそらく、.Net 3.5ではWinApiが_cdeclを表していたのに対し、現在は__stdcallを表している可能性があります。

ただし、P / Invokeを使用するときは、常に__stdcall(つまり、WINAPI)を指定する必要があることを覚えているので、実際にはそうではないと思います。なぜそれが.Net3.5で機能したのかよくわかりません。(たぶん、DllImportは当時怠惰で、呼び出し規約を「見落としていた」-それは奇妙だろう)

于 2011-02-17T21:23:05.977 に答える