我ながらバカらしくなってきた。昨日のをうpしてから「そんなわけはない」的に再度調べ直していたら、冗談じゃない、まったく素直で簡単な方法がやはりあったのである。
昨日のコードの上にbutton2を追加したと思ってもらいたい。さらにC#のコードに以下を追加する(以下は必要な部分のみ)。
Cのソースとヘッダにはそれぞれ以下のような内容を追加する(同じく必要な部分のみ)。
・ヘッダ
・ソース
せっかく作ったインデントつきタグ付けプログラムがいらないくらい単純である。これを実行してbutton2を押せば、最初に「what?」というメッセージボックスが表示され、そのOKボタンを押すと続けてDOS窓に
が表示される。まったく何の懸念も問題もない。結論を特筆大書しておこう。C#メソッドは普通にCから呼び出せるのである。唯一注意すべきことがあるとすれば、Marshal.GetFunctionPointerForDelegateで得られる関数ポインタを使ってC側から呼び出す際は「__stdcall」修飾子が必須だということである(上のふたつのtypedefの記述にあるのがそれである。もっとも、最初のパラメタなし関数はそれなしでも動く。またこれらのことはVisualStudioのヘルプにも──かなりひどい日本語だが──書かれている)。
上の例もパラメタや戻り値は整数だが、この方法ならもっと複雑なパラメタや戻り値をもつことも簡単にできる。Cから呼ばれることがわかっている関数なら、複雑な構造体はアンマネージ領域のポインタをIntPtrで受け取り、関数の中で明示的にマーシャリングして使えばいい。戻り値や副作用も同様である。そうでないC#関数はラッパーを用意してその中でマーシャリングした上で本体を呼び出せばよい。
こんな素直で単純で、しかも確実な方法があることに今まで気づかなかったというのは、まったく我ながらマヌケもいいところだった、ということにはなる。マヌケという点については言い訳はしないでおこう。事実マヌケだとしか言いようがない。「素振り」の比喩で言えば、外人投手を相手にするときはバットを逆さに持って振らなければならないと思い込んでいたようなものである。
とはいえ、おそらくはわたしと同程度のマヌケが、少なくとも日本にはたくさんいるはずだということも間違いなさそうである。C#とC(もしくはC++)の相互運用なんて仕事でさんざんやっているわけだが、過去にわたしが仕事でかかわった誰ひとりとしてこのことに言及した人間はいなかったわけである(そもそもmarshalないしmarshalingという単語の意味から説明してかからなくてはならないことの方が多い、というか、してもほとんど誰ひとり理解した顔をしない)。
昨日のコードの上にbutton2を追加したと思ってもらいたい。さらにC#のコードに以下を追加する(以下は必要な部分のみ)。
using System; using System.Windows.Forms; using System.Runtime.InteropServices; namespace call_csharp_method_from_c_function { public partial class form: Form { public form() { InitializeComponent(); } public delegate int func(int a,int b); public static void one() { MessageBox.Show("what?"); } public static int two(int a,int b) { return 2*a+3*b; } [DllImport("cmodule.dll")] public static extern int cmodule_exec2(IntPtr p); [DllImport("cmodule.dll")] public static extern int cmodule_exec3(IntPtr p); public void button2_click(object sender,EventArgs e) { MethodInvoker mi = one; func mi2 = two; IntPtr p = Marshal.GetFunctionPointerForDelegate(mi); cmodule_exec2(p); p = Marshal.GetFunctionPointerForDelegate(mi2); cmodule_exec3(p); } } // end of class } // end of namespace |
Cのソースとヘッダにはそれぞれ以下のような内容を追加する(同じく必要な部分のみ)。
・ヘッダ
#ifndef _CMODULE_H_ #define _CMODULE_H_ #ifdef CMODULE_EXPORTS #define CMODULE __declspec(dllexport) #else #define CMODULE __declspec(dllimport) #endif (中略) typedef void (__stdcall *voidfunc)(void); typedef int (__stdcall *binaryfunc)(int a,int b); // function prototypes #ifdef __cplusplus extern "C" { #endif // __cplusplus CMODULE int __stdcall cmodule_exec2(voidfunc f); CMODULE int __stdcall cmodule_exec3(binaryfunc f); #ifdef __cplusplus } #endif // __cplusplus #endif // _CMODULE_H_ |
・ソース
#include "cmodule.h" CMODULE int __stdcall cmodule_exec2(voidfunc f) { f(); return 0; } CMODULE int __stdcall cmodule_exec3(binaryfunc f) { printf("f(1,2) = %d\n",f(1,2)); return 0; } |
せっかく作ったインデントつきタグ付けプログラムがいらないくらい単純である。これを実行してbutton2を押せば、最初に「what?」というメッセージボックスが表示され、そのOKボタンを押すと続けてDOS窓に
f(1,2) = 8 |
が表示される。まったく何の懸念も問題もない。結論を特筆大書しておこう。C#メソッドは普通にCから呼び出せるのである。唯一注意すべきことがあるとすれば、Marshal.GetFunctionPointerForDelegateで得られる関数ポインタを使ってC側から呼び出す際は「__stdcall」修飾子が必須だということである(上のふたつのtypedefの記述にあるのがそれである。もっとも、最初のパラメタなし関数はそれなしでも動く。またこれらのことはVisualStudioのヘルプにも──かなりひどい日本語だが──書かれている)。
上の例もパラメタや戻り値は整数だが、この方法ならもっと複雑なパラメタや戻り値をもつことも簡単にできる。Cから呼ばれることがわかっている関数なら、複雑な構造体はアンマネージ領域のポインタをIntPtrで受け取り、関数の中で明示的にマーシャリングして使えばいい。戻り値や副作用も同様である。そうでないC#関数はラッパーを用意してその中でマーシャリングした上で本体を呼び出せばよい。
こんな素直で単純で、しかも確実な方法があることに今まで気づかなかったというのは、まったく我ながらマヌケもいいところだった、ということにはなる。マヌケという点については言い訳はしないでおこう。事実マヌケだとしか言いようがない。「素振り」の比喩で言えば、外人投手を相手にするときはバットを逆さに持って振らなければならないと思い込んでいたようなものである。
とはいえ、おそらくはわたしと同程度のマヌケが、少なくとも日本にはたくさんいるはずだということも間違いなさそうである。C#とC(もしくはC++)の相互運用なんて仕事でさんざんやっているわけだが、過去にわたしが仕事でかかわった誰ひとりとしてこのことに言及した人間はいなかったわけである(そもそもmarshalないしmarshalingという単語の意味から説明してかからなくてはならないことの方が多い、というか、してもほとんど誰ひとり理解した顔をしない)。