C++→C(DLL)→C#(クラスライブラリ) の続き

id:lord_hollow:20100712:p1 の続き。
ネイティブアプリから、C#で実装した何らかの機能を呼び出すにはどうすればいいのか、という話。
前回書いた方法では、呼び出し元になるexeと、仲介役になるDLL(C++/CLI)と、そして機能を実装するDLL(C#)という三つのファイルが存在してしまうという問題がありました。つまり、DLLが一個多い。
この状態にどうしても納得できない私が三年半の歳月を通じてついにDLLを一つ減らす方法にたどり着きましたのでそれをご紹介しましょう。心して聞くように。

きっかけは、↓を見つけたことでした。
http://blogs.msdn.com/b/junfeng/archive/2006/05/20/599434.aspx
内容は特に解説しませんが、C++/CLIのリンカのオプションで、CSC(C#コンパイラ)の/target:module オプションを付けることで生成される .netmodule というファイルを渡せば双方がリンクできる、ということが書いてあります。
こいつらにできることがVisualStudio(MSBuild)にできないはずがない。

というわけで、さっそく私がたどりついたその方法を解説していきます。こうすればもっと簡単だよね〜という情報があったら是非ほしいところ。

スタートは、前回のラストの状態です。__declspec(dllexport)がついた関数を定義しているC++/CLIのプロジェクトが一つと、そのプロジェクトによって参照設定されているC#のクラスライブラリプロジェクトが一つある状態。


1. C++/CLIのプロジェクトからC#のプロジェクトへの参照設定を解除する。これによって、当然C++/CLI側のプロジェクトはコンパイルエラー(クラスが見つからない!)になる。

2. この操作によって見えなくなったクラスの定義を、C++/CLIで頑張ってヘッダファイルに書く。宣言部だけでOK。実装は不要。この作業はめんどくさいので、あらかじめC++/CLIから必要になるC#の定義は、できるだけシンプルにしておく。つまり、C++/CLIがexportしているAPIをまとめた小さなクラスかインターフェースにしておけ。この定義を変更するとC#C++/CLIの双方に変更が必要になるので、インターフェースのほうがいいかも。インターフェースにする場合は、gcnewができないので別途ファクトリメソッドが必要になりますが、その辺はまぁ各自工夫していただくということで。

3. このヘッダファイルを必要に応じてincludeすれば、少なくともコンパイルは通る。でもリンクが通らない。実装がないから。この実装を、C#から持ってくる必要がある。

4. おもむろにC#のプロジェクトファイル(csproj)をテキストエディタで開いて、OutputType を Library から Module に書き換える。ついでに、PlatformTarget が X86 になっている場合は AnyCPU に戻しておく。

5. この状態でC#のプロジェクトをビルドすると、.DLLの代わりに .netmodule というファイルが出力される。この中に、さっきC++/CLIで頑張って宣言を書いたクラスの実装が入っているはずだが、確認する術を私は持たない。

6. C++/CLIのプロジェクトに戻って、リンカオプションの「入力」、「追加の依存ファイル」のところに、先ほど生成した.netmoduleのパスを設定してからビルドすると・・・定義が間違っていない限り、リンクも通る!!.netmoduleってのはようは静的リンクライブラリなので、実行時には不要!!!ちなみに、2.で書いたクラスの定義が間違っていると、メタデータが一致しないとか言って怒られます。4.でAnyCPUになっていないと、LINK1302エラーが出ます。


以上です。C#で書いたクラスの定義を再度C++/CLIで書き直さなければならないのが面倒ですが、refrectorとにらめっこして頑張りましょう。

参考のために、嵌ったところを下に列挙しておきます。

  • event に [MethodImpl(MethodImplOptions::Synchronized)] という属性が勝手についてる場合がある-ポインタを渡せないので引数で結果を受けたければ参照型にしなければならない。C++/CLIの参照型はたとえば int% と書く。
  • クラスそのものの可視性もC++/CLIC#で合わせる必要がある。もともとクラスライブラリだったのでpublicがついているはずだが、静的リンクするようになるのでpublicでなくても別にかまわない。
  • bool型を使う場合は、C#の宣言の側に[MarshalAs(UnmanagedType.U1)]とか[return: MarshalAs(UnmanagedType.U1)]を付けなければならない

#20160717追記
2.は不要で、代わりにC++/CLIソースコードに #using "xxxx.netmodule"と書いて(追加の#usingディレクトリ(/AL)設定も確認!)おけばインテリセンスも効く。

PhantomPain3

スレには何度か投入してますが、chaika用のスキンを作っています。ライセンスどうしようかな。BSDでいいか。
https://github.com/lordhollow/PhantomPain
なんか規制かかっててかけなかったのでこっちに書いた。

寒ー

なんか・・・いきなり寒いな。
先週まで半そでだったのに今日は長袖でも凍え死ぬかと思った。

追伸(嫁に書けといわれたので)
娘が予防接種を受けたらしいのですが、「四歳だから!」と言って泣かなかったそうです。すごいね。

また短編で新キャラが・・・

ハサミの回を見るに、なんかとんでもない何かがある・・・様にみせかけてなんもなさそうだな。