メンバー関数をスレッド関数に

スレッドを起こす古いプログラムを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);
    }
};

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