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
マネージ型

  値・参照\デフォ
      ルト        Public          private
   アクセス指定子

    参照型        ref struct;     ref class;


    値型        value struct;   value class;



• class structの使い方はC++相当
• 値型・参照型はその前につけるvalue/refで決ま
  る。
• 参照型のハンドルは型名の後ろに^をつけて表
  す
             わんくま同盟 東京勉強会 #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).ToPo
inter();

               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
Wrap6 メモリストレージ CvMemStorage

• コンストラクタでストレージ生成
• ファイナライザで全解放
• 割り当て済みストレージの解放関数
 – void cvClearMemStorage(CvMemStorage^ storage);




                わんくま同盟 東京勉強会 #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                   ~CvRect()
{                                         { this->!CvRect();}
  public:
    // ~~略~~                              !CvRect()
    CvRect()                              {
    {                                        if(!noalloc_)
      noalloc_ = false;                      {
      this-> ptr_ = new ::CvRect;              delete ptr_;
    }                                        }
                                             this->ptr_ = nullptr;
   CvRect( ::CvRect* pRect)               }
   {                                    private:
     noalloc_ = true;                      bool noalloc_ ;
     this->ptr_ = pRect;
   }                                    Internal:
                                           ::CvRect* ptr_;
                                    }


                         わんくま同盟 東京勉強会 #69
Wrap7 複合型メンバ変数をプロパティとして公開
            (CvContour::rect)
• プロパティのgetterではポインタを受け取
  るコンストラクタを用いて、
  ラッパーオブジェクトを作成(gcnew)する。
• ポインタを受け取るコンストラクタで初期
  化した場合は非破壊フラグを立て、ファイ
  ナライザでdeleteしないようにする。
• setterではvalue側の内部ポインタの指す値
  をコピーする。


          わんくま同盟 東京勉強会 #69
wrap8
輪郭スキャンクラス
 CvContourScanner
輪郭スキャン関数群
cvStartFindContours
 cvFindNextContour
cvEndFindContours
      のラップ


    わんくま同盟 東京勉強会 #69
Wrap8 輪郭スキャン関連




     使い方が複雑なので
まずC++側でどのように使うかを確認




      わんくま同盟 東京勉強会 #69
Wrap8 輪郭スキャン関連(C++)
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);



                         わんくま同盟 東京勉強会 #69
Wrap8 輪郭スキャン関連(C++)
• CvContourScanner は_CvContourScanner*のtypedef
• CvContourScannerの定義はヘッダファイルにない
• cvStartFindContours(img,storage)は閾値処理済み画像と、
  輪郭オブジェクトの確保につかうメモリストレージを引数にとり
  CvContourScanner を生成
• cvFindNextContourは、輪郭を一つ取得
• cvEndFindContoursはCvContourScanner を破棄し、
  全部の輪郭データを取得 メンバ変数をたどって別の輪郭の取得も
可能
• cvClearMemStorage(storage)で輪郭データの解放+メモリストレー
  ジにメモリを返還
• cvFindNextContour,cvEndFindContourの戻り値は
  オブジェクトの先頭のメモリ構造を同じにすることで行う疑似継承
  における 疑似基底クラス

                わんくま同盟 東京勉強会 #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
ご清聴ありがとうございました




    わんくま同盟 東京勉強会 #69

T69 c++cli ネイティブライブラリラッピング入門

  • 1.
    C++/CLI ネイティブライブラリラッピン グ入門 暁 紫電 わんくま同盟 東京勉強会 #69
  • 2.
    自己紹介 • HN 暁紫電 • Twitter @akatukisiden • 年齢 25歳 • フリープログラマー • 使用言語 C++、C++/CLI、C# • 現在のお仕事 Kinect(OpenNI)、OpenCV、MIDI わんくま同盟 東京勉強会 #69
  • 3.
    マネージ コードとネイティブ コード 相互運用テクノロジ • P/Invoke • COM • C++/CLI わんくま同盟 東京勉強会 #69
  • 4.
    Platform Invoke (P/Invoke) •DLLからC スタイルのネイティブ関数を呼び出す。 • ヘッダファイルの情報を .NET側に用意する必要がある。 • .NET Frameworkの内部でよく使用されている • 引数のマネージ←→ネイティブ変換コストに加 え 一回の呼び出しでx86命令10~30個分の オーバーヘッドが係る • どんな関数でも正常に呼び出せるわけではない わんくま同盟 東京勉強会 #69
  • 5.
    COM相互運用 • I Unknown •マネージ コードからCOMインターフェイスを使 用、 またはマネージAPIを COMインターフェイス として 公開する機能 • Office関連や、WinRTなどで使われている • 時間コストはC++/CLIと同程度 わんくま同盟 東京勉強会 #69
  • 6.
    C++/CLI • Cヰ • マネージコードおよびネイティブ コード が混在するアセンブリを作成する • P/Invokeとは違い、見た目は普通の マネージオブジェクトにすることが可能 • おそらく、どんなクラス・関数でもラップ 可能 わんくま同盟 東京勉強会 #69
  • 7.
    今回は 画像処理ライブラリOpenCVのラップを通し て C++/CLIを用いた .NETでのネイティブコードの利用について 紹介します。 ※ ヘッダファイルに宣言と定義、両方まとめて書いてい ますが、実際は分けて書いてください わんくま同盟 東京勉強会 #69
  • 8.
    マネージ型 値・参照\デフォ ルト Public private アクセス指定子 参照型 ref struct; ref class; 値型 value struct; value class; • class structの使い方はC++相当 • 値型・参照型はその前につけるvalue/refで決ま る。 • 参照型のハンドルは型名の後ろに^をつけて表 す わんくま同盟 東京勉強会 #69
  • 9.
  • 10.
    Wrap1 最低限のラップ (CvSize) publicref class CvSize { public: CvSize(void) { ptr = new ::CvSize; } ~CvSize() { this -> !CvSize(); } !CvSize() { delete ptr; } internal: ::CvSize* ptr; }; わんくま同盟 東京勉強会 #69
  • 11.
    Wrap1 最低限のラップ (CvSize) •ref class • ネイティブオブジェクトのポインタを internalメンバとして持たせる。 • コンストラクタでオブジェクトを生成 • デストラクタ/ファイナライザで破棄 • デストラクタ(~Class())はDispose(true)相当 • ファイナライザ(!Class())はDispose(false) 相当 • ファイナライザは通常の関数として呼び出し可 能 わんくま同盟 東京勉強会 #69
  • 12.
    Wrap2 基本型メンバ変数の公開 (CvSize) publicref 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
  • 13.
    Wrap2 基本型メンバ変数の公開 (CvSize) •メンバ変数はプロパティとして公開する • Internalポインタを通してメンバ変数にアクセス • 基本型はマネージ、ネイティブ間で互換性有り わんくま同盟 東京勉強会 #69
  • 14.
    OpenCV画像クラス IplImageをラップする わんくま同盟 東京勉強会 #69
  • 15.
    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
  • 16.
    Wrap3 ポインタで管理するクラス (IplImage) • 独自の生成関数、解放関数を持ち、 インスタンスはポインタで管理するクラス • 生成・解放関数自体はコンストラクタ、 ファイナライザ内で呼び出す。 • ダブルポインタを引数に取る関数(解放関数)は そのままではガベコレでアドレスが変わる 可能性があるというエラーが発生するので pin_ptr<T*>でアドレスを固定する わんくま同盟 東京勉強会 #69
  • 17.
    画像ファイル読み込み関数 cvLoadImageを コンストラクタとしてラップ わんくま同盟 東京勉強会 #69
  • 18.
    Wrap4 文字列変換(cvLoadImage) public refclass IplImage { // ~~略~~ public: IplImage(System::String^ str, int iscolor) { const char* chars = (const char*) System::Runtime::InteropServices::Marshal ::StringToHGlobalAnsi(str).ToPo inter(); this->ptr = ::cvLoadImage(chars ,iscolor); System::Runtime::InteropServices::Marshal ::FreeHGlobal(System::IntPtr((vo id*)chars)); } // ~~略~~ }; わんくま同盟 東京勉強会 #69
  • 19.
    Wrap4 文字列変換(cvLoadImage) • System.Stringからchar*に変換 •マネージ・アンマネージの変換用の関数をまと めたMarshalクラスのメンバを使用 • IntPtr Marshal::StringToHGlobalAnsi(string); String の内容をアンマネージ メモリにコピーし、 コピー時に ANSI 形式に変換します。 • void Marshal::FreeHGlobal(IntPtr); アンマネージメモリから割り当てられたメモリを 解放します。 わんくま同盟 東京勉強会 #69
  • 20.
    しきい値処理関数 cvThresholdをラップする わんくま同盟 東京勉強会 #69
  • 21.
    Wrap5 関数のラップ(cvThreshold) public refclass 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
  • 22.
    Wrap5 関数のラップ(cvThreshold) • オブジェクトを引数に取るものは 内部ポインタを(必要なら*をつけて)渡す。 • 基本型引数はそのまま渡せる。 わんくま同盟 東京勉強会 #69
  • 23.
  • 24.
    表示用関数郡 using namespace System::Runtime::InteropServices; publicref 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
  • 25.
    表示用関数 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
  • 26.
    実行コード 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
  • 27.
    実行コード C# IplImage.cvThreshold(loadedImage, img2, 205, 255, 0); GUI.cvShowImage( "window" , img2); GUI.cvWaitKey(0); GUI.cvDestroyWindow( "window" ); } } わんくま同盟 東京勉強会 #69
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
    Wrap6 輪郭クラスはメモリストレージ CvMemStorage内に確保されるので まずはCvMemStorageをラップ わんくま同盟 東京勉強会 #69
  • 33.
    Wrap6 メモリストレージ CvMemStorage publicref 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
  • 34.
    Wrap6 メモリストレージ CvMemStorage •コンストラクタでストレージ生成 • ファイナライザで全解放 • 割り当て済みストレージの解放関数 – void cvClearMemStorage(CvMemStorage^ storage); わんくま同盟 東京勉強会 #69
  • 35.
    wrap7 輪郭クラスCvContourは生成・破棄の方法が 特殊なので 先にそのメンバ変数rect(CvRect型)をラップ し プロパティとして公開する わんくま同盟 東京勉強会 #69
  • 36.
    Wrap7 複合型メンバ変数をプロパティとして公開 (CvContour::rect) Class AとそのメンバBをラップしたものが わんくま同盟 東京勉強会 #69
  • 37.
    Wrap7 複合型メンバ変数をプロパティとして公開 (CvContour::rect) ClassAのラッパーがBのラッパーをメンバに持つよ うに見えるようにする必要がある わんくま同盟 東京勉強会 #69
  • 38.
    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
  • 39.
    Wrap7 複合型メンバ変数をプロパティとして公開 (CvContour::rect) public ref class CvRect ~CvRect() { { this->!CvRect();} public: // ~~略~~ !CvRect() CvRect() { { if(!noalloc_) noalloc_ = false; { this-> ptr_ = new ::CvRect; delete ptr_; } } this->ptr_ = nullptr; CvRect( ::CvRect* pRect) } { private: noalloc_ = true; bool noalloc_ ; this->ptr_ = pRect; } Internal: ::CvRect* ptr_; } わんくま同盟 東京勉強会 #69
  • 40.
    Wrap7 複合型メンバ変数をプロパティとして公開 (CvContour::rect) • プロパティのgetterではポインタを受け取 るコンストラクタを用いて、 ラッパーオブジェクトを作成(gcnew)する。 • ポインタを受け取るコンストラクタで初期 化した場合は非破壊フラグを立て、ファイ ナライザでdeleteしないようにする。 • setterではvalue側の内部ポインタの指す値 をコピーする。 わんくま同盟 東京勉強会 #69
  • 41.
  • 42.
    Wrap8 輪郭スキャン関連 使い方が複雑なので まずC++側でどのように使うかを確認 わんくま同盟 東京勉強会 #69
  • 43.
    Wrap8 輪郭スキャン関連(C++) 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); わんくま同盟 東京勉強会 #69
  • 44.
    Wrap8 輪郭スキャン関連(C++) • CvContourScannerは_CvContourScanner*のtypedef • CvContourScannerの定義はヘッダファイルにない • cvStartFindContours(img,storage)は閾値処理済み画像と、 輪郭オブジェクトの確保につかうメモリストレージを引数にとり CvContourScanner を生成 • cvFindNextContourは、輪郭を一つ取得 • cvEndFindContoursはCvContourScanner を破棄し、 全部の輪郭データを取得 メンバ変数をたどって別の輪郭の取得も 可能 • cvClearMemStorage(storage)で輪郭データの解放+メモリストレー ジにメモリを返還 • cvFindNextContour,cvEndFindContourの戻り値は オブジェクトの先頭のメモリ構造を同じにすることで行う疑似継承 における 疑似基底クラス わんくま同盟 東京勉強会 #69
  • 45.
    Wrap8 輪郭抽出関数のラップ public refclass 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
  • 46.
    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
  • 47.
    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
  • 48.
    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
  • 49.
    Wrap8 輪郭抽出関数のラップ(C++/CLI) private: bool noalloc_; internal: _CvContourScanner* ptr_; } struct _CvContourScanner{}; わんくま同盟 東京勉強会 #69
  • 50.
    Wrap8 輪郭抽出関数のラップ(C++/CLI) public refclass 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
  • 51.
    輪郭抽出関数のラップ • CvContourScannerがヘッダファイルに定義がないので、 自分で空の構造体を作成する。 (ポインタのサイズが分かれば正常に動作するはず) • CvContourScannerオブジェクトはcvStartFindContours()で作成し、 cvEndFindContours()で破棄する cvEnd~を呼び忘れても良いように、データが破棄されていなけれ ば、 ファイナライザで呼び出す。 • 輪郭データの破壊はcvEndFindContours()で返されるオブジェクト (全輪郭の代表に)に対して::cvClearMemStorage()を呼び出すこと で行う。(ファイナライザで呼び出す。) • cvFindNextContourの戻り値の輪郭データは開放してはいけないので cvFindNext~では非破壊コンストラクタ、 cvEndFind~では通常のコンストラクタで輪郭オブジェクトを 作成する(cvContour) わんくま同盟 東京勉強会 #69
  • 52.
  • 53.
    輪郭・矩形 描画関連 (C++/CLI) publicref 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
  • 54.
    続・実行コード 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
  • 55.
    続・実行コード 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
  • 56.
    続・実行コード 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
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
    まとめ • ネイティブオブジェクトはポインタで管理し コンストラクタでnew,ファイナライザでdeleteす る • ポインタ等の露出する部分はinternalにして 外部から見えないようにする。 • メンバ変数はプロパティとして公開 • ダブルポインタはpin_ptrでアドレスを固定 • char*↔System.String等はMarshalクラスで変換 わんくま同盟 東京勉強会 #69
  • 64.
    まとめ • 複合型メンバ変数は、ポインタを受け取るコン ストラクタを使い、マネージド型(ラッパークラ ス)のプロパティとして公開する。 解放は親が解放される事で行われるので、 ファイナライザで開放しないようにする。 • マネージドオブジェクトが消滅するとき ラップしたネイティブオブジェクトも 解放(delete)してよいのか注意し、 • よくない場合は、 コンストラクタでフラグを立てるなどの方法で、 ファイナライザでの解放を防ぐ わんくま同盟 東京勉強会 #69
  • 65.
    ご清聴ありがとうございました わんくま同盟 東京勉強会 #69