WinCE/WEC の Secure LoaderWEC 7 の WMP サンプルアプリケーション

ネイティブコードから C# へのコールバックについて補足

2014/02/16 koga

今回は、2012/05/14 のエントリ(「ネイティブコードから C# のメソッドをコールバック」)についての補足を書きます。補足するのは、次の二点です:

・コールバック関数のコーリングコンベンション
・ネイティブコードへのデリゲートオブジェクト渡し

それぞれについて、以下に記します。

■コールバック関数のコーリングコンベンション
2012/05/14 のエントリでは、マネージドコードからアンマネージコードに渡すコールバック関数の関数ポインタを得る API として、GetFunctionPointerForDelegate() を紹介しました。この API で取り出す関数ポインタに対しては、アンマネージコード側からみた場合、留意すべき点があります。リファレンスページの “Remarks” の項にある注意書きです。

 Marshal.GetFunctionPointerForDelegate Method
 http://msdn.microsoft.com/en-us/library/at4fb09f(v=vs.90).aspx

上のページの “Remarks” の項には、次の注意書きがあります:

The delegate d is converted to a function pointer that can be passed to unmanaged code using the __stdcall calling convention.

つまり、アンマネージコード(ネイティブコード)から見た場合、マネージコードから渡される関数ポインタのコーリングコンベンション(呼出規約)は __stdcall になるので、アンマネージコード側では、受け取る関数ポインタの型を、__stdcall 付きで宣言しておく必要がある、というわけです。__stdcall をはじめ、Visual C++ の C/C++ コンパイラにおけるコーリングコンベンションの説明は、リファレンスの次のページをご覧ください。

 Argument Passing and Naming Conventions
 http://msdn.microsoft.com/ja-jp/library/984×0h58(v=vs.90).aspx

デフォルトのコーリングコンベンションは、スタックに積まれた手続きの引数を、手続きを呼び出した側がスタックから取り除く __cdecl であり、呼び出された手続きが取り除く __stdcall とは異なりますので、その点に注意して下さい。

さて、アンマネージコードの既存のライブラリがあり、そのライブラリに対してコールバック関数を設定するマネージドコードを実装しようとした際、既存のライブラリが受け取るコールバック関数が __cdecl であった場合は、どうすればよいのでしょうか?そのままでは、コーリングコンベンションが合っていないため、実行時エラーを引き起こす可能性があります。

ご安心下さい。その問題は、マネージドコードからの呼び出し方を変えることで解決できます。これは、冒頭に示した二点目の補足に関係します。

注意:実は、WinCE/WEC では、後述するようにコーリングコンベンションの問題はありません。この問題を考慮する必要があるのは、フル版の .NET Framework の場合だけなのです。

■ネイティブコードへのデリゲートオブジェクト渡し
2012/05/15 のエントリでは、マネージドコードからアンマネージコードへ渡す関数ポインタを得るために、delegate 実体に GetFunctionPointerForDelegate() を適用する必要があると述べました。しかし、この呼び出しは必須ではなく、delegate 実体をそのまま渡すことができます。MSDN にある .NET Framework プログラミングガイドにある、「アンマネージコードとの相互運用」にあるサンプルコードでも、delegate 実体をそのまま渡しています。2012/05/15 のエントリを書いた時点では、そのことを理解していませんでした。ごめんなさい。

MSDN にあるサンプルコードは、次のページをご覧ください:

 Callback のサンプル(マネージドコード)
 http://msdn.microsoft.com/ja-jp/library/5zwkzwf4(v=vs.90)

 PinvokeLib.dll(アンマネージコード)
 http://msdn.microsoft.com/ja-jp/library/as6wyhwt(v=vs.90).aspx

さて、コーリングコンベンションに話を戻します。.NET Framework には、マネージドコードとアンマネージコードの間での、(アンマネージコードにおける)コーリングコンベンションを表現する UnmanagedFunctionPointerAttribute というクラスがあります。このクラスを使って、delegate のコーリングコンベンションを変更できます。具体的には、delegate を宣言する際、次のように属性記述すれば、delegate のコーリングコンベンションを __cdecl に設定できるようです:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool FPtr( int value );


しかし、この UnmanagedFunctionPointerAttribute は、WinCE/WEC 環境、つまり .NET Compact Framework ではサポートされていません。さらに、UnmanagedFunctionPointerAttribute クラスのコンストラクタ引数である、enumeration 型の CallingConvention は、.NET Compact Framework の場合には、メンバとして Winapi しか持たず、これは __cdecl に対応するとリファレンスに書かれているのです。

 CallingConvention Enumeration
 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.callingconvention(v=vs.90).aspx

このリファレンスには、デフォルトのコーリングコンベンションを示す Winapi は、「Windows 環境」、つまりフル版の .NET Framework では __stdcall に対応し、WinCE/WEC 環境(.NET Compact Framework)では __cdecl に該当するという旨の説明があります。これは一体、どいうことなのでしょう?Win32 API は、WINAPI 付きで宣言されており、__stdcall のコーリングコンベンションを設定されています。一方、UnmanagedFunctionPointerAttribute が __cdecl にしか対応しないのでは、両者が整合しないということです。

実は、WinCE/WEC のコーリングコンベンションは __cdecl だけなのです。このことは、次のヘッダファイルを見れば分かります:
 %_WINCEROOT%/public/common/sdk/inc/windef.h

windef.h には、次の行があり、’__stdcall’ が __cdecl の別名として定義されているのです。

#ifdef UNDER_CE
#define __stdcall __cdecl // Note this doesn't match the desktop definition
#define _stdcall __cdecl // Note this doesn't match the desktop definition
#endif


従って、.NET Compact Framework の場合には、コーリングコンベンションのことを意識する必要は、ありません。とはいえ、.NET Compact Framework 版と .NET Framework 版とで、アンマネージコードを含めてソースコードを共有できるように、可搬なコードを書く場合には、.NET Framework 版でも問題なく動作するよう、コーリングコンベンションを意識する必要があります。

■まとめ
今回は、2012/05/14 のエントリに対して二つの補足を書きました。二つのうち、コーリングコンベンションについては、WinCE/WEC では考慮せずともよいのですが、Win7 や Win8 でも動かせる可搬なコードを書く場合には、考慮する必要があります。

もう一つの、デリゲート渡しについては、わざわざ GetFunctionPointerForDelegate() を呼ぶ必要はない、というのが結論です。これは、2012/05/14 のエントリで、どちらかといえば、不適切な内容だったと思います。同様の見解が、.NET Framework に関する次の記事にも書かれています:

 C# からコールバック関数を使う C の関数を呼ぶ
 http://sgry.jp/pgarticles/cs_pinvoke_callback.html

今回の補足ですが、2012/05/15 のエントリに頂いていたコメントを拝見して書きました。コメントを頂いたのは、2013/05/14 ですから、半年以上の時間が経ってしまったのですが、参考になりました。有難うございます。

Entry Filed under: アプリケーション開発

Leave a Comment

Required

Required, hidden

Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackback this post  |  Subscribe to the comments via RSS Feed


Categories

Links

Posts by Authors

Recent Posts

Calendar

2014年2月
« 2月   8月 »
 1
2345678
9101112131415
16171819202122
232425262728  

Posts by Month

Posts by Category

Meta