t69 c++cli ネイティブライブラリラッピング入門
TRANSCRIPT
わんくま同盟 東京勉強会 #69
C++/CLI
ネイティブライブラリラッピング入門
暁 紫電
わんくま同盟 東京勉強会 #69
自己紹介
• HN 暁 紫電
• Twitter @akatukisiden
• 年齢 25歳
• フリープログラマー
• 使用言語
C++、C++/CLI、C#
• 現在のお仕事
Kinect(OpenNI)、OpenCV、MIDI
わんくま同盟 東京勉強会 #69
マネージ コードとネイティブ コード相互運用テクノロジ
• P/Invoke
• COM
• C++/CLI
わんくま同盟 東京勉強会 #69
Platform Invoke (P/Invoke)
• DLLからC スタイルのネイティブ関数を呼び出す。
• ヘッダファイルの情報を
.NET側に用意する必要がある。
• .NET Frameworkの内部でよく使用されている
• 引数のマネージ←→ネイティブ変換コストに加え
一回の呼び出しでx86命令10~30個分の
オーバーヘッドが係る
• どんな関数でも正常に呼び出せるわけではない
わんくま同盟 東京勉強会 #69
COM相互運用
• I Unknown
• マネージ コードからCOMインターフェイスを使用、
またはマネージAPIを COMインターフェイスとして
公開する機能
• Office関連や、WinRTなどで使われている
• 時間コストはC++/CLIと同程度
わんくま同盟 東京勉強会 #69
C++/CLI
• Cヰ
• マネージ コードおよびネイティブ コードが混在するアセンブリを作成する
• P/Invokeとは違い、見た目は普通の
マネージオブジェクトにすることが可能
• おそらく、どんなクラス・関数でもラップ可能
わんくま同盟 東京勉強会 #69
今回は
画像処理ライブラリOpenCVのラップを通して
C++/CLIを用いた
.NETでのネイティブコードの利用について
紹介します。※ ヘッダファイルに宣言と定義、両方まとめて書いてい
ますが、実際は分けて書いてください
わんくま同盟 東京勉強会 #69
マネージ型
• class structの使い方はC++相当
• 値型・参照型はその前につけるvalue/refで決まる。
• 参照型のハンドルは型名の後ろに^をつけて表す
値・参照\デフォルト
アクセス指定子Public private
参照型 ref struct; ref class;
値型 value struct; value class;
わんくま同盟 東京勉強会 #69
矩形等のサイズを表す構造体CvSizeをラップする
わんくま同盟 東京勉強会 #69
Wrap1 最低限のラップ (CvSize)
public ref class CvSize{
public:CvSize(void){ ptr = new ::CvSize; }
~CvSize(){ this -> !CvSize(); }
!CvSize(){ delete ptr; }
internal:::CvSize* ptr;
};
わんくま同盟 東京勉強会 #69
Wrap1 最低限のラップ (CvSize)
• ref class
• ネイティブオブジェクトのポインタを
internalメンバとして持たせる。
• コンストラクタでオブジェクトを生成
• デストラクタ/ファイナライザで破棄
• デストラクタ(~Class())はDispose(true)相当
• ファイナライザ(!Class())はDispose(false) 相当
• ファイナライザは通常の関数として呼び出し可能
わんくま同盟 東京勉強会 #69
Wrap2 基本型メンバ変数の公開 (CvSize)
public ref class CvSize{
// ~~略~~public:
property int width{
int get(){ return ptr_->width;}void set( int value ){ ptr_->width = value; }
}property int height
{int get(){return ptr_->height;}
void set(int value){ptr_->height = value; }}
internal:::CvSize* ptr_
};
わんくま同盟 東京勉強会 #69
Wrap2 基本型メンバ変数の公開 (CvSize)
• メンバ変数はプロパティとして公開する
• Internalポインタを通してメンバ変数にアクセス
• 基本型はマネージ、ネイティブ間で互換性有り
わんくま同盟 東京勉強会 #69
OpenCV画像クラスIplImageをラップする
わんくま同盟 東京勉強会 #69
Wrap3 ポインタで管理するクラス(IplImage)
public ref class IplImage{
public:IplImage(CvSize^ size,int depth,int channels){ ptr = ::cvCreateImage(*size->ptr,depth,channels);}
~IplImage(){ this->!IplImage(); }
!IplImage(){
pin_ptr<::IplImage*> pin_Image = &ptr;::cvReleaseImage(pin_Image);
}internal:
::IplImage* ptr;};
わんくま同盟 東京勉強会 #69
Wrap3 ポインタで管理するクラス(IplImage)
• 独自の生成関数、解放関数を持ち、
インスタンスはポインタで管理するクラス
• 生成・解放関数自体はコンストラクタ、
ファイナライザ内で呼び出す。
• ダブルポインタを引数に取る関数(解放関数)は
そのままではガベコレでアドレスが変わる
可能性があるというエラーが発生するので
pin_ptr<T*>でアドレスを固定する
わんくま同盟 東京勉強会 #69
画像ファイル読み込み関数cvLoadImageを
コンストラクタとしてラップ
わんくま同盟 東京勉強会 #69
Wrap4 文字列変換(cvLoadImage)public ref class IplImage{
// ~~略~~public:
IplImage(System::String^ str, int iscolor){
const char* chars = (const char*)System::Runtime::InteropServices::Marshal
::StringToHGlobalAnsi(str).ToPointer();
this->ptr = ::cvLoadImage(chars ,iscolor);
System::Runtime::InteropServices::Marshal::FreeHGlobal(System::IntPtr((vo
id*)chars));}
// ~~略~~};
わんくま同盟 東京勉強会 #69
Wrap4 文字列変換(cvLoadImage)
• System.Stringからchar*に変換
• マネージ・アンマネージの変換用の関数をまとめたMarshalクラスのメンバを使用
• IntPtr Marshal::StringToHGlobalAnsi(string);
String の内容をアンマネージ メモリにコピーし、
コピー時に ANSI 形式に変換します。
• void Marshal::FreeHGlobal(IntPtr);
アンマネージメモリから割り当てられたメモリを
解放します。
わんくま同盟 東京勉強会 #69
しきい値処理関数cvThresholdをラップする
わんくま同盟 東京勉強会 #69
Wrap5 関数のラップ(cvThreshold)public ref class IplImage{// ~~略~~
double cvThreshold( IplImage^ src, IplImage^ dst,double threshold, double max_value, int
threshold_type ){
return ::cvThreshold(src->ptr, dst->ptr,threshold, max_value,
threshold_type);}
};
わんくま同盟 東京勉強会 #69
Wrap5 関数のラップ(cvThreshold)
• オブジェクトを引数に取るものは
内部ポインタを(必要なら*をつけて)渡す。
• 基本型引数はそのまま渡せる。
わんくま同盟 東京勉強会 #69
とりあえず一回実行してみる
※ 表示に必要な関数は予めラップ済み
わんくま同盟 東京勉強会 #69
表示用関数郡
using namespace System::Runtime::InteropServices;public ref class GUI{
public:static int cvNamedWindow( System::String^ str, int flags ){
const char* chars = (const char*)Marshal::StringToHGlobalAnsi(str).ToPointer();
int r = ::cvNamedWindow(chars,flags);Marshal::FreeHGlobal(System::IntPtr((void*)chars));
return r;}
わんくま同盟 東京勉強会 #69
表示用関数
static void cvDestroyWindow( System::String^ str){
const char* chars = (const char*)Marshal::StringToHGlobalAnsi(str).ToPointer();
::cvDestroyWindow( chars );Marshal::FreeHGlobal( System::IntPtr((void*)chars));
}
static void cvShowImage( System::String^ name,IplImage^ image){
const char* chars = (const char*)Marshal::StringToHGlobalAnsi(name).ToPointer();
::cvShowImage( chars ,image->ptr);Marshal::FreeHGlobal( System::IntPtr((void*)chars));
}static int cvWaitKey(int delay){ return ::cvWaitKey(delay); }
};
わんくま同盟 東京勉強会 #69
実行コード C#
class Program{
static void Main( string[] args){
GUI.cvNamedWindow( "window" , 1);
IplImage loadedImage = new IplImage( "wankuma.png” , 0);
GUI.cvShowImage( "window” , loadedImage);GUI.cvWaitKey(0);
CvSize size = new CvSize();size.width = loadedImage.width;size.height = loadedImage.height;IplImage img2 = new IplImage(size, loadedImage.depth,
loadedImage.nChannels );
わんくま同盟 東京勉強会 #69
実行コード C#
IplImage.cvThreshold(loadedImage, img2, 205, 255, 0);
GUI.cvShowImage( "window" , img2);GUI.cvWaitKey(0);
GUI.cvDestroyWindow( "window" );}
}
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
二値画像から輪郭とその外接矩形を取得する
わんくま同盟 東京勉強会 #69
Wrap6
輪郭クラスはメモリストレージCvMemStorage内に確保されるのでまずはCvMemStorageをラップ
わんくま同盟 東京勉強会 #69
Wrap6 メモリストレージ CvMemStorage
public ref class CvMemStorage{
public:CvMemStorage( int block_size){ ptr_ = ::cvCreateMemStorage(block_size); }
~CvMemStorage() { this->!CvMemStorage();}
!CvMemStorage(){
pin_ptr< ::CvMemStorage* > p = &(this->ptr_ );::cvReleaseMemStorage( p ) ;
}static void cvClearMemStorage(CvMemStorage^ storage){ ::cvClearMemStorage(storage->ptr_ ); }
internal:::CvMemStorage* ptr_;
};
わんくま同盟 東京勉強会 #69
• コンストラクタでストレージ生成
• ファイナライザで全解放
• 割り当て済みストレージの解放関数
– void cvClearMemStorage(CvMemStorage^ storage);
Wrap6 メモリストレージ CvMemStorage
わんくま同盟 東京勉強会 #69
wrap7
輪郭クラスCvContourは生成・破棄の方法が特殊なので
先にそのメンバ変数rect(CvRect型)をラップし
プロパティとして公開する
わんくま同盟 東京勉強会 #69
Wrap7 複合型メンバ変数をプロパティとして公開(CvContour::rect)
Class AとそのメンバBをラップしたものが
わんくま同盟 東京勉強会 #69
Wrap7 複合型メンバ変数をプロパティとして公開(CvContour::rect)
ClassAのラッパーがBのラッパーをメンバに持つように見えるようにする必要がある
わんくま同盟 東京勉強会 #69
Wrap7 複合型メンバ変数をプロパティとして公開(CvContour::rect)
public ref class CvContour{
public:// ~~略~~property CvRect^ rect{
CvRect^ get(){
return gcnew CvRect(&ptr_->rect);}void set(CvRect^ value){
*ptr_->rect = *(value->ptr_);}
}internal:
::CvContour* ptr_;};
わんくま同盟 東京勉強会 #69
Wrap7 複合型メンバ変数をプロパティとして公開(CvContour::rect)
public ref class CvRect{
public:// ~~略~~CvRect(){
noalloc_ = false;this-> ptr_ = new ::CvRect;
}
CvRect( ::CvRect* pRect){
noalloc_ = true;this->ptr_ = pRect;
}
~CvRect(){ this->!CvRect();}
!CvRect(){
if(!noalloc_){
delete ptr_;}this->ptr_ = nullptr;
}private:
bool noalloc_ ;
Internal:
::CvRect* ptr_;
}
わんくま同盟 東京勉強会 #69
Wrap7 複合型メンバ変数をプロパティとして公開(CvContour::rect)
• プロパティのgetterではポインタを受け取るコンストラクタを用いて、ラッパーオブジェクトを作成(gcnew)する。
• ポインタを受け取るコンストラクタで初期化した場合は非破壊フラグを立て、ファイナライザでdeleteしないようにする。
• setterではvalue側の内部ポインタの指す値をコピーする。
わんくま同盟 東京勉強会 #69
wrap8
輪郭スキャンクラスCvContourScanner
輪郭スキャン関数群cvStartFindContours
cvFindNextContour
cvEndFindContours
のラップ
わんくま同盟 東京勉強会 #69
Wrap8 輪郭スキャン関連
使い方が複雑なのでまずC++側でどのように使うかを確認
わんくま同盟 東京勉強会 #69
IplImage img_th = 閾値処理後の画像CvContourScanner scanner = cvStartFindContours(img_th, storage);
CvContour* contour;do{
contour = reinterpret_cast< CvContour*>( cvFindNextContour(scanner) );
if(contour != nullptr){
CvRect rect = contour->rect; }
}while (contour != nullptr);contour = reinterpret_cast<CvContour*>(cvEndFindContours(&scanner));cvClearMemStorage(contour->storage);
Wrap8 輪郭スキャン関連(C++)
わんくま同盟 東京勉強会 #69
• CvContourScanner は_CvContourScanner*のtypedef
• CvContourScannerの定義はヘッダファイルにない
• cvStartFindContours(img,storage)は閾値処理済み画像と、
輪郭オブジェクトの確保につかうメモリストレージを引数にとり
CvContourScanner を生成
• cvFindNextContourは、輪郭を一つ取得
• cvEndFindContoursはCvContourScanner を破棄し、
全部の輪郭データを取得 メンバ変数をたどって別の輪郭の取得も可能
• cvClearMemStorage(storage)で輪郭データの解放+メモリストレージにメモリを返還
• cvFindNextContour,cvEndFindContourの戻り値は
オブジェクトの先頭のメモリ構造を同じにすることで行う疑似継承
における 疑似基底クラス
Wrap8 輪郭スキャン関連(C++)
わんくま同盟 東京勉強会 #69
Wrap8 輪郭抽出関数のラップ
public ref class CvContourScanner{
public:static const int DEFAULT_HEADER_SIZE = sizeof( ::CvContour);
CvContourScanner(IplImage^ image,CvMemStorage^ storage,int header_size, int mode, int method,CvPoint^ offset)
{ptr_ =::cvStartFindContours(image->ptr_,storage->ptr_,
header_size,mode,method,*offset->ptr_);}
static CvContourScanner^ cvStartFindContours(IplImage^ image,CvMemStorage^ storage, int header_size,
int mode, int method,CvPoint^ offset){
return = gcnew CvContourScanner(image,storage,header_size,mode,method,offset);
}
わんくま同盟 東京勉強会 #69
Wrap8 輪郭抽出関数のラップ
CvContour^ cvFindNextContour(){
::CvContour* contour= reinterpret_cast< ::CvContour*>( ::cvFindNextContour(this->ptr_) );
if(contour != nullptr){
return gcnew CvContour(contour);}else{
return nullptr;}
}
static CvContour^ cvFindNextContour(CvContourScanner^ scanner){
return scanner->cvFindNextContour();}
わんくま同盟 東京勉強会 #69
Wrap8 輪郭抽出関数のラップ
CvContour^ cvEndFindContours(){
pin_ptr< ::_CvContourScanner* > pp = &(this->ptr_);::CvContour* ptr = reinterpret_cast< ::CvContour*>
(::cvEndFindContours(pp));pp = nullptr; this->ptr_ = nullptr;CvContour^ contour = gcnew CvContour();contour->ptr_ = ptr;return contour;
}
static CvContour^ cvEndFindContours(CvContourScanner^ scanner){
return scanner->cvEndFindContours();}
わんくま同盟 東京勉強会 #69
Wrap8 輪郭抽出関数のラップ
~CvContourScanner(){
this->!CvContourScanner();}!CvContourScanner(){
if(!noalloc_){
if(this->ptr_ != nullptr){
pin_ptr< ::_CvContourScanner* > pp = &(this->ptr_);::CvContour* contour = reinterpret_cast< ::CvContour*>
( ::cvEndFindContours(pp) );::cvClearMemStorage(contour->storage);this->ptr_ = nullptr;
}}
}
わんくま同盟 東京勉強会 #69
Wrap8 輪郭抽出関数のラップ(C++/CLI)
private:bool noalloc_;
internal:_CvContourScanner* ptr_;
}
struct _CvContourScanner{};
わんくま同盟 東京勉強会 #69
Wrap8 輪郭抽出関数のラップ(C++/CLI)
public ref class CvContour{
public:CvContour(){ noalloc_ = false; this->ptr_ = nullptr; }
~CvContour() {this->!CvContour();}!CvContour(){
if(!noalloc_) { ::cvClearMemStorage(ptr_->storage); }}
private:bool noalloc_;
internal:CvContour( ::CvContour* ptr){noalloc_ = true; this->ptr_ = ptr; }::CvContour* ptr_;
};
わんくま同盟 東京勉強会 #69
輪郭抽出関数のラップ
• CvContourScannerがヘッダファイルに定義がないので、
自分で空の構造体を作成する。
(ポインタのサイズが分かれば正常に動作するはず)
• CvContourScannerオブジェクトはcvStartFindContours()で作成し、cvEndFindContours()で破棄する
cvEnd~を呼び忘れても良いように、データが破棄されていなければ、
ファイナライザで呼び出す。
• 輪郭データの破壊はcvEndFindContours()で返されるオブジェクト(全輪郭の代表に)に対して::cvClearMemStorage()を呼び出すことで行う。(ファイナライザで呼び出す。)
• cvFindNextContourの戻り値の輪郭データは開放してはいけないので
cvFindNext~では非破壊コンストラクタ、
cvEndFind~では通常のコンストラクタで輪郭オブジェクトを
作成する(cvContour)
わんくま同盟 東京勉強会 #69
もう一回実行してみる
わんくま同盟 東京勉強会 #69
輪郭・矩形 描画関連 (C++/CLI)
public ref class IplImage{
public:// ~~略~~
static void cvDrawContours(IplImage^ img, CvContour^ contour,CvScalar^ external_color,CvScalar^ hole_color,
int max_level, int thickness ,int line_type, CvPoint^ offset){::cvDrawContours(img->ptr_,reinterpret_cast<CvSeq*>
(contour->ptr_), *external_color->ptr_, *hole_color->ptr_,max_level,thickness,line_type, *offset->ptr_);
}static void cvRectangleR(IplImage^ img, CvRect^ r,
CvScalar^ color,int thickness, int line_type, int shift){
::cvRectangleR(img->ptr_,*r->ptr_,*color->ptr_,thickness,line_type,shift);
}};
わんくま同盟 東京勉強会 #69
続・実行コード C#
class Program{
static void Main(string[] args){
// ~~略~~CvMemStorage storage = new CvMemStorage(0);CvContourScanner scanner = new CvContourScanner
(img2,storage,CvContourScanner.DEFAULT_HEADER_SIZE,1,2,new CvPoint(0,0));
List<CvRect> list = new List<CvRect>();CvContour scanningContour = null;
わんくま同盟 東京勉強会 #69
続・実行コード C#do{
scanningContour = scanner.cvFindNextContour();if (scanningContour!= null){
list.Add(scanningContour.rect);}
}while (scanningContour != null);
CvContour endContour = scanner.cvEndFindContours();IplImage.cvDrawContours(img2,endContour,new CvScalar(64,64,64,64),
new CvScalar(255,255,255,255), 1,2,8, new CvPoint(0, 0));
GUI.cvShowImage( "window" , img2);GUI.cvWaitKey(0);
わんくま同盟 東京勉強会 #69
続・実行コード C#
foreach(CvRect r in list){
IplImage.cvRectangleR(img2, r, new CvScalar(128, 128,128,128), 2, 8, 0);
}
endContour.Dispose();storage.Dispose();
GUI.cvShowImage( "window" , img2);GUI.cvWaitKey(0);
img2.Dispose();GUI.cvDestroyWindow( "window" );
}};
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
わんくま同盟 東京勉強会 #69
まとめ
わんくま同盟 東京勉強会 #69
まとめ
• ネイティブオブジェクトはポインタで管理しコンストラクタでnew,ファイナライザでdeleteする
• ポインタ等の露出する部分はinternalにして
外部から見えないようにする。
• メンバ変数はプロパティとして公開
• ダブルポインタはpin_ptrでアドレスを固定
• char*↔System.String等はMarshalクラスで変換
わんくま同盟 東京勉強会 #69
まとめ
• 複合型メンバ変数は、ポインタを受け取るコンストラクタを使い、マネージド型(ラッパークラス)のプロパティとして公開する。解放は親が解放される事で行われるので、ファイナライザで開放しないようにする。
• マネージドオブジェクトが消滅するときラップしたネイティブオブジェクトも解放(delete)してよいのか注意し、
• よくない場合は、コンストラクタでフラグを立てるなどの方法で、ファイナライザでの解放を防ぐ
わんくま同盟 東京勉強会 #69
ご清聴ありがとうございました