スレッドを起こす古いプログラムをC++化しようとして、ちょっと躓いてしまいました。備忘録のメモです。
Windowsで、スレッドを起こすAPIは CreateThread、もしくは通常はCRTライブラリの_beginthreadexを利用しますが・・・、これらのAPIに渡す、スレッド関数(スレッドの開始アドレス)の呼び出し規約は、__cdeclもしくは__stdcallです。要はグローバル関数(自由関数)なわけですが、C++化する上で、できるだけグローバル関数を減らしたいと思いました。
プログラム自体をC++化する上で、単純にクラス内に静的な(staticな)メンバを追加してこれをスレッド関数にして、新しいスレッドに渡すパラメータにオブジェクトのインスタンスポインタを渡せば、最小の変更でうまく行きます。
が、もっと簡単に、、オブジェクト・インスタンスのメンバ関数をスレッド関数にしたい場合は、ちょっと工夫がいるようです。
というわけで、上記のようなプロキシ的な静的メンバ関数を設置して、インスタンスメンバー関数をスレッドに渡すようにしたコードを書いてみました。
/* SimpleThreading.hpp 簡単なスレッドクラスです。継承させて使用します。 追記(2010/4/10):致命的な欠陥があるので実際には使わないように。参考程度に見てください。 メンバー関数ポインタとパラメータを CSimpleThreadingのメンバ変数にいったん保存して スレッド関数(ThreadStartProxyから参照している部分がスレッドアウトになってます(笑) class CMyObject : public CSimpleThreading<CMyObject> { ... }; */ #include <windows.h> #include <process.h> template<typename T> class CSimpleThreading { typedef UINT_PTR (T::*MEM_FUN)(LPVOID); LPVOID m_pvParameter; MEM_FUN m_ThreadStart; static UINT_PTR __stdcall ThreadStartProxy(LPVOID pvSimpleThreading) { CSimpleThreading *pSimpleThreading = (CSimpleThreading*)pvSimpleThreading; //修正しました。(10/4/16) return ((T*)pSimpleThreading->*(pSimpleThreading->m_ThreadStart))(pSimpleThreading->m_pvParameter); } protected: //引数:メンバ関数ポインタ、スレッド処理に渡すパラメータ、サスペンド HANDLE BeginThreadEx(MEM_FUN ThreadStart,LPVOID pvParam,BOOL bSuspended = FALSE) { if(!ThreadStart) return NULL; m_pvParameter = pvParam; m_ThreadStart = ThreadStart; return (HANDLE)_beginthreadex(NULL, 0, CSimpleThreading::ThreadStartProxy, (LPVOID)this, bSuspended ? CREATE_SUSPENDED : 0, NULL); } };
このコードの要旨は、静的メンバ関数を_beginthreadex()APIに渡し、その中で実際にスレッドに処理させたいメンバー関数をコールする、というごく一般的なものです。
CSimpleThread::BeginThreadExで、メンバ関数ポインタとパラメータをプロパティとして保存しています。あとは、静的メンバー関数内で、そのメンバー関数ポインタをパラメータを引数としてコールしています。
メンバー関数ポインタをコールするには、どのインスタンスのメンバー関数をコールするのか、といった情報が必要なので、必ずpObject->*や、Object.*といったものが必要です。メンバー関数内でコールする場合も、this->*は省略できないようです。
実際には、上記クラスを継承させて使用します。
/* ThreadTest.cpp 使用例 */ #include "SimpleThreading.hpp" class CMain : public CSimpleThreading<CMain> { public: UINT_PTR ThreadStart(LPVOID pvParam) { //スレッド処理... return 0; } DWORD Run() { DWORD dwWait = 0; HANDLE hThread = BeginThreadEx(&CMain::ThreadStart,NULL); if(hThread == NULL) return 0; return WaitForSingleObject(hThread,INFINITE); } };
実際にはプロキシ的に静的メンバ関数をかましているので、メンバー関数をスレッド関数にしているわけではありませんが、ちょこっとしたプログラムにはこれで十分です(^^