#freeze
参考にさせていただいたページ:
- http://www7b.biglobe.ne.jp/~robe/cpphtml/html03/cpp03057.html
- http://d.hatena.ne.jp/kasei_san/20070612/p1

「pthread」では、メンバ関数をスレッド化出来ない。正確には、staticメンバ関数であれば、pthread_createの引数に出来るのだが、それはなんか気持ち悪い。というか、クラス化した意味が無いし、C++っぽくない。

そこで、上記のHPで紹介されている手法を使わせてもらうことにする。

ざっくりいうと、staticなメンバ関数を間に噛ませることで、非スタティックな関数をスレッドとして動くようにしようというのが本手法。

で、自分でも使ってみたのが次のコード。

OpenCVを使っているので、かなり読みにくい。

 #include <cv.h>
 #include <highgui.h>
 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "fbiad.h"
 #include <fstream>
 #include <string>
 #include <iomanip>
 #include <pthread.h>
 #include <iostream>

 
 extern "C"
 {
     typedef void* (*ThreadFunc_t)(void*);
 }

 
 static pthread_mutex_t mutex;  //同期用にmutexを確保

 
 using namespace std;

 
 /* IEEE1394Camera用クラス */
 class IEEE1394Camera
 {
 private:
   CvCapture *capture;
   IplImage *frame;
   CvVideoWriter *vw;
   double w, h;
   int c, num;
   CvFont font;
   char str[64];

 
 public:
   /* コンストラクタ */
   IEEE1394Camera(int index)
   {
     w = 320, h = 240;
     num = 0;
     capture = cvCaptureFromCAM (index);
   }
   /* デストラクタ */
   ~IEEE1394Camera()
   {
     cvReleaseCapture (&capture);
   }
   /* スレッド起動用静的メンバ関数 */
   static void* video_thread(void *arg)
   {
     IEEE1394Camera* Ccam = (IEEE1394Camera*)arg;
     Ccam->video_io();
     return NULL;
   }
   /* 実際にスレッドで動かしたい関数 */
   void* video_io ()
   {
     //キャプチャサイズを設定
     cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_WIDTH, w);
     cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_HEIGHT, h);
     //ビデオライタ構造体を作成
     vw = cvCreateVideoWriter ("cap.avi", CV_FOURCC ('X', 'V', 'I', 'D'), 15, cvSize ((int) w, (int) h));
     cvInitFont (&font, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7);
     cvNamedWindow ("Capture", CV_WINDOW_AUTOSIZE);
     printf("video_get_ready...\n");
   pthread_mutex_lock( &mutex ); //mainでロックされているので、開放されるまで待機
   pthread_mutex_unlock( &mutex );
     //カメラから画像をキャプチャし,ファイルに出力
     while (1) {
       frame = cvQueryFrame (capture);
       snprintf (str, 64, "%03d[frame]", num);
       cvPutText (frame, str, cvPoint (10, 20), &font, CV_RGB (0, 255, 100));
       cvWriteFrame (vw, frame);
       cvShowImage ("Capture", frame);
       num++;
       c = cvWaitKey (10);
       if (c >= 0)
         break;
     }
     //書き込みを終了し,構造体を解放
     cvReleaseVideoWriter (&vw);
     cvDestroyWindow ("Capture");
     return 0;
   }
 };

 
 /* メイン関数 */
 int main(int argc, char **argv)
 {
   int cam_index;
   if (argc == 1 || (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0])))
     cam_index = (argc == 2 ? argv[1][0] - '0' : 0);
   IEEE1394Camera Ccam(cam_index);

 
 pthread_mutex_init( &mutex, NULL ); //mutexを初期化
 pthread_mutex_lock( &mutex ); //mutexにlockをかける
   pthread_t video_t;
   pthread_create( &video_t, NULL, (ThreadFunc_t)IEEE1394Camera::video_thread, (void *)&Ccam );
   getchar();  //キー入力を待つ
 pthread_mutex_unlock( &mutex ); //lockを解除
   pthread_join( video_t, NULL );
 pthread_mutex_destroy(&mutex);          /*  ミューテックス破棄  今のLinuxでは意味はないが念のため */
   return 0;
 }

*コンパイル [#o44483c7]

 $ g++ `pkg-config --cflags opencv` `pkg-config --libs opencv` pthread_video_io_2.cpp

*説明 [#yc6c13ff]

ここで重要なのは、

 extern "C"
 {
     typedef void* (*ThreadFunc_t)(void*);
 }

と

   /* スレッド起動用静的メンバ関数 */
   static void* video_thread(void *arg)
   {
     int v_index = (int)arg;
     IEEE1394Camera Ccam(v_index);
     Ccam.video_io();
     IEEE1394Camera* Ccam = (IEEE1394Camera*)arg;
     Ccam->video_io();
     return NULL;
   }
   /* 実際にスレッドで動かしたい関数 */
   void* video_io ()   pthread_create( &video_t, NULL, (ThreadFunc_t)IEEE1394Camera::video_thread, (void *)cam_index );
   pthread_create( &video_t, NULL, (ThreadFunc_t)IEEE1394Camera::video_thread, (void *)&Ccam );

の部分。

まず、staticメンバ変数であれば、pthread_createでスレッドとして起動できるので、「スレッド起動用静的メンバ関数」を準備。

が、UNIXの場合pthread_create(POSIX準拠のスレッドライブラリでスレッドを構築する関数)は型として「extern "C" void*(*)(void*)」を要求するため、型が違うというエラーが出る。

なので、C言語形式にリンケージ指定する必要があるが、メンバ関数にはextern "C"が使えない。そこで、こちら(http://b-51.hp.infoseek.co.jp/programing.html)で紹介されている、typedefを使った回避方法を使わせていただいた。

以下引用。

>   以下のようにextern "C"付きでtypedefするとそのtypedefされたシンボルはC言語リンケージ指定までを含みます。

>   extern "C"
>   {
>     typedef void* (*ThreadFunc_t)(void*);
>   }
>       ・
>       ・
>       ・
>     sts = pthread_create(&tid,NULL,(ThreadFunc_t)CTest::ThreadEntry,this);
>
>   これはThreadFunc_tという型名に「extern "C" void*(*)(void*)」(戻り値がvoid*で引数がvoid*一つのCリンケージ指定関数へのポインタ)という意味を持たせます。ですので、最後の行のようにstaticメンバ関数をこの型名でキャストして渡してあげればワーニングも出ることなく、スレッドのエントリポイントにメンバ関数が使えます。
>
>あとは「スレッド起動用静的メンバ関数」に実際にスレッドの実効処理を行うのは別のメンバ関数を呼び出す処理を書けばOK!