C#からCの関数をコールする場合、おもに2通りの方法があります。
- CをDLLとして公開し、C#からP/Invokeを使う
- CをDLLorLibとして公開し、C++/CLIで.NETラッパを作成しC#から.NET呼出し
後者の場合、C++/CLIについていくつか知っておくべきことがありますので簡単にご紹介。
1.デストラクタとファイナライザ
C++/CLIの・・・はC#の・・・に対応しており、、などとややこしい記述がたくさんあるのでここでは要点だけをまとめておきます。。C++/CLIのデストラクタ・ファイナライザについて覚えておくべきことは以下3点。
- C++/CLIのデストラクタ、ファイナライザどちらもケースによって呼ばれないことがある
- そのため、アンマネージリソースを扱うクラスを定義する場合、デストラクタからファイナライザをコールし、ファイナライザ内でアンマネージリソースを解放する
- またアンマネージリソースを扱うクラスを使う場合、クラスを明示的にdeleteする。(C#からそのクラスを使う場合disposeする)
以上。C#からC++/CLIを参照することはあっても逆参照はないと思いますので上記内容があれば十分かと。上記2点目を実際に定義する場合、以下のようになります。
PatternSearch.cpp
#include "stdafx.h" #include "PatternSearch.h" namespace OpenCvWrap{ // ----------------------------------- // -- public func implementation -- // ----------------------------------- PatternSearch::PatternSearch(System::Drawing::Bitmap^ srcImage) { // IplImage* cvTemplate = Converter::CreateIplImage(srcImage); } // デストラクタ PatternSearch::~PatternSearch() { this->!PatternSearch(); } // ファイナライザ PatternSearch::!PatternSearch() { if (cvTemplate != NULL){ pin_ptr<IplImage*> p = &cvTemplate; cvReleaseImage(p); cvTemplate = NULL; } } }
補足ですが上のクラス内cvTemplateはOpenCVのIplImageへのポインタです。このようにC++/CLIにポインタをメンバ変数として持った場合、そのアドレスを&を使って取得しようとすると
‘cli::interior_ptr<Type>’から’IplImage **’に変換できません。
というエラーが出ます。GCによってアドレスが変わることを防いでいるためですね。そのため上のようにpin_ptrを使ってアドレスを固定して呼び出しをしてやる必要があります。
2.デザイナが出力するファイル
C++/CLIでクラスを新規作成したときにできる.hファイルはこんな感じです。
Converter.h
#pragma once ref class Converter { public: Converter(); };
せめて.NETのクラス定義なんだから名前空間とクラスのアクセス修飾子は明示しましょう。
#pragma once namespace OpenCvWrap{ public ref class Converter { public: Converter(); }; }
あとは.hファイルにはむやみにusing namespace System::Drawing;など書かないこと。C++/CLIのクラス間では.hファイルのインクルードなので名前がかぶってしまう可能性があります。
3.Nativeな構造体は持てない
C++/CLIのクラスにCで定義した構造体をメンバーに持つことはできません。保持できるのはWORD, SHORTなどのプリミティブ型と構造体へのポインタだけです。コンストラクタでnewしてやるなど適宜フリーストアからメモリを確保しましょう。
4.IntPtrとBYTE*の変換例
例えばSystem::Drawing::BitmapクラスとOpenCVのIplImageとの変換処理を作るとき、24BitのフルカラーBmpからモノクロ8bitへは以下のようなstatic関数で変換が可能です。
Converter.cpp
namespace OpenCvWrap{ IplImage* Converter::BitmapToIplImage(System::Drawing::Bitmap^ src){ IplImage* dst = cvCreateImage(cvSize(src->Width, src->Height), IPL_DEPTH_8U, 1); int dstStride = dst->widthStep; System::Drawing::Rectangle^ srcRect = gcnew System::Drawing::Rectangle(Point::Empty, src->Size); BitmapData^ bmpData = src->LockBits(*srcRect, ImageLockMode::ReadWrite, PixelFormat::Format24bppRgb); void* pvSrc = (void*)bmpData->Scan0; BYTE* pbySrc = static_cast<BYTE*>(pvSrc); int srcStride = bmpData->Stride; for (int y = 0; y < dst->height; y++){ for (int x = 0; x < dst->Width; x++){ dst->imageData[y * dstStride + x] = (BYTE)( ( 118 * pbySrc[y * srcStride + 3 * x + 0] + 600 * pbySrc[y * srcStride + 3 * x + 1] + 306 * pbySrc[y * srcStride + 3 * x + 2] ) / 1024 ); } } src->UnlockBits(bmpData); return dst; } }
ここでbmpData->Scan0を一旦void*で受けて、その後static_cast<BYTE*>としていますがこれはIntPtrからBYTE*への直接変換ができないためです。これをしようとすると、
‘static_cst’ : ‘System::IntPtr’ から ‘BYTE *’に変換できません。
と言われてしまいます。
だいたいこれくらい知ってれば困らないんじゃないかなーと。