メンバ関数をマルチスレッドに投げる

C++とWindowsAPIの相性の悪い部分として、API関数がCがベースになっている点。おかげさまでOOPで作るといろいろと支障が・・・。コールバック関数とかその典型じゃないかと思います。関数ポインタって・・・orz

普通の関数ポインタとメンバ関数のポインタってどうやら型が違うみたいで受け取ってくれないんですね。コンパイルエラーが返ってきます。しかし静的関数(クラスメソッド)ならコンパイルが通るしちゃんと作動します。しかし静的関数しか取れないんじゃOOPの意味ないし・・・。

そういうわけで関数ポインタを受け取るAPI関数になんとかメンバ関数を渡せないかと模索していたら何とかできました。今回はその代表としてスレッドを生成するAPI関数CreateThreadに渡す方法を紹介します。

まず、マルチスレッドとかCreateThreadの仕様とかは今回本質ではないので省きます。詳しいサイトはいくらでもあるのでそちらを参照してください。CreateThreadは第3引数として関数ポインタを受け取ります。関数ポインタはLPVOIDを引数として戻り値はDWORD型になります。これはどうしようもありません。ので、メンバ関数をうまくラッピングすれば動くはずです。そのラッピング結果が以下の通り。

template< class C, class H >
struct MEM{
	C* pClass;
	DWORD (C::* pMethod)(H);
	H Param;
};

template< class C, class H >
DWORD WINAPI ThreadFunc( LPVOID vdParam ){
	mem< C, H >* p = reinterpret_cast< mem< C, H >* >( vdParam );
	return (p->pClass->*p->pMethod)( p->Param );
}

えっと、C++をそれなりにやりこんでいる人でも少々わけ分からんソースかもしれません。私も書いててわけわからんかったし(滝汗 何をやっているかというと、まずラッピング関数はThreadFuncの部分で、テンプレート引数としてメンバ関数を持っているクラスとメンバ関数の引数の型を渡します。んで、上にある構造体テンプレートですがこいつにクラスインスタンスのポインタとメンバ関数ポインタとその引数を格納してCreateThreadの第4引数にポインタ渡しをします。CreateThreadの第4引数ということはスレッド関数の引数に渡される、つまりThreadFuncのLPVOID部分に入ってきます。のでここで構造体の中身を取り出してメンバ関数を呼び出します。これで間接的にメンバ関数をスレッド化することに成功しました。

んで具体的な使い方ですが

class ThreadClass{
public:
	DWORD ThreadFuncInt( int i );
	DWORD ThreadFuncPointer( double* pD );
	DWORD ThreadFuncString( std::string st );
};

こういったクラスがあったとして3つのメンバ関数を同時に走らせたい場合、以下の通り。

ThreadClass* pTFC = new ThreadClass;

MEM< ThreadClass, int > Mem1;
Mem1.pClass = pTFC;
Mem1.pMethod = &(ThreadClass::ThreadFuncInt);
Mem1.Param = 10;
DWORD dwID1;
HANDLE hThreadHandle1 = ::CreateThread(NULL , 0 , ThreadFunc< ThreadClass, int > , &Mem1 , 0 , &dwID1 );

MEM< ThreadClass, double* > Mem2;
Mem2.pClass = pTFC;
Mem2.pMethod = &(ThreadClass::ThreadFuncPointer);
Mem2.Param = new double[10];
DWORD dwID2;
HANDLE hThreadHandle1 = ::CreateThread(NULL , 0 , ThreadFunc< ThreadClass, double* > , &Mem2 , 0 , &dwID2 );

MEM< ThreadClass, std::string > Mem3;
Mem3.pClass = pTFC;
Mem3.pMethod = &(ThreadClass::ThreadFuncString);
Mem3.Param = "うぼぁー";
DWORD dwID3;
HANDLE hThreadHandle1 = ::CreateThread(NULL , 0 , ThreadFunc< ThreadClass, std::string > , &Mem3 , 0 , &dwID3 );

とまあこんな感じになると思います。欠点としては引数として参照型とconstが取れません。どうしても必要ならMEM構造体にコンストラクタをつけて

template< class C, class H >
struct MEM{
	MEM( H h ):Param( h ){}
	C* pClass;
	DWORD (C::* pMethod)(H);
	H Param;
};

とすれば多分うまくいくんじゃないかな?かな?
実用性の程は・・・どうなんでしょうね?(^^;