スレッドを起こす古いプログラムを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);
}
};
実際にはプロキシ的に静的メンバ関数をかましているので、メンバー関数をスレッド関数にしているわけではありませんが、ちょこっとしたプログラムにはこれで十分です(^^