コマンドプロンプトのログを取る(その3)

コマンドプロンプトのログを取る(その2)
コンソール(CMD.EXE)のログを取る (不完全版)
の続きです。

その2で、僕が望んだ動作はほぼ達成できました。だいぶ不完全だけど・・・。
その2の不満点は、CTRL-Cを押すとcmdlog.exe自体がガサッと落ちてしまうんです(子プロセス諸とも終了してしまう)。やはり、CTRL-Cを押すと、子プロセス側で走っているコンソールプロセスだけ死んで欲しいのは当然ですよねぇ。
ってことで、こういう場合は、親プロセス(cmdlog.exe)がCTRL-Cを受け取ったら、親プロセス側は何もせず、CTRL-Cイベント(シグナルかな?)をそのまま子プロセスに渡してしまうのがお約束かとおもいます。

で、そのまんまのSetConsoleCtrlHandlerというAPIがありますので、これを使います。

このAPIは名前のとおり、コンソールプロセスでCtrl-CとかCtrl-Homeを受け取ったときに呼び出される関数をセットできるもので、既存のハンドラルーチン(関数)を追加、削除ができます。
なわけで、

/*CTRLハンドラ*/
BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
  BOOL bRetVal = FALSE;

  if(dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT)
    bRetVal = GenerateConsoleCtrlEvent(dwCtrlType,Context::ProcessInformation.dwProcessId);
  
  return bRetVal;
}

のようなシグナルハンドラ(っていうのかな?)を追加すれば、Ctrl-Cを子プロセスに送って自分(親プロセス)は何もしないってことができます。良かった。パチパチ。

ってなわけで・・・

若干手直ししたソースはこちらから。。。(cmdlog03.zip)
全てのソースはこっちに移動

あとは・・・そうですね・・・いいかげん、コマンド引数からログファイル名を指定するようにせんといかんな・・・ハードコードなんてダサすぎる・・・(ーー;;;


コマンドプロンプトのログを取る(完結)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る (その2)
コンソール(CMD.EXE)のログを取る (不完全版)

コマンドプロンプトのログを取る (その2)

コンソール(CMD.EXE)のログを取る (不完全版) の続きです。

標準出力と標準エラー出力をファイルに書き出せたところまではできてタイムアップ。標準入力が書き出せていませんでした。今回は、入力バッファをファイルに書き出してから、リダイレクトしたパイプハンドルに書き出せば事は足ります。

で、あまりにもソースが汚くなってきたので整理して、テストコードが完成。パイプハンドルをラップした構造体とスレッドに渡すコンテキストを別ファイルにして整理。

/*******************************************************************

 ■コマンドプロンプトのすべての操作をテキストファイルへ書き出します。

  ※ほとんどエラーチェック・処理をしていないテストコード
  ※書き出すファイルは、#define LOGFILE TEXT("~") で指定。

********************************************************************/

#include <windows.h>

//Cランタイム
#include <tchar.h>
#include <stdio.h>

//パイプ、スレッドに渡すパラメータをラップした構造体を定義したヘッダー
#include "pipe.h"
#include "context.h"

#define CLOSEHANDLE(X) {if(X)CloseHandle(X);}
#define BUFFER_SIZE 1024
#define SPIN_COUNT 4000

//書き出すファイル名の指定
#define LOGFILE TEXT("cmdlog.txt")

//標準入出力・ファイルハンドルへの読取と書込スレッド
DWORD WINAPI ReadAndWriteProc(LPVOID lpParameter)
{
  Context *context = (Context*)lpParameter;

  TCHAR pBuffer[BUFFER_SIZE] = {0};
  DWORD dwRead = 0,dwWrite = 0;
  BOOL bRet = FALSE;
  while(!(context->bThreadMustTerminate))
    {
      do
        {
          bRet = ReadFile(context->hRead,(PVOID)pBuffer,BUFFER_SIZE,&dwRead,NULL);
          if(dwRead > 0)
            {
              EnterCriticalSection(&(context->CriticalSection));
              if(context->hFile)
                WriteFile(context->hFile,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              if(context->hWrite)
                WriteFile(context->hWrite,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              LeaveCriticalSection(&(context->CriticalSection));
            }
        }
      while(bRet == TRUE && dwRead > 0);
    }

  return 0;
}

//ファイルを書込用にオープン(同期)
HANDLE OpenLogFile(PCTSTR szLogName)
{
  return CreateFile(szLogName,
                    GENERIC_WRITE,
                    0,
                    NULL,
                    CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
}

//環境変数COMSPECからコマンドプロンプトのパスを得て起動
BOOL CreateCommandPrompt(STARTUPINFO& si,PROCESS_INFORMATION& pi)
{
  BOOL bRetVal = FALSE;

  //CMD.EXEのパスを格納するバッファ
  TCHAR szCmdPath[MAX_PATH+1] = {0};

  //CMD.EXEのパスを環境(システム)変数から得る。
  if(0 == GetEnvironmentVariable(TEXT("COMSPEC"),szCmdPath,MAX_PATH+1))
    goto cleanup;
  
  bRetVal = CreateProcess(NULL,
                          szCmdPath,
                          NULL,
                          NULL,
                          TRUE,
                          NORMAL_PRIORITY_CLASS,
                          NULL,
                          NULL,
                          &si,
                          &pi);

cleanup:
  return bRetVal;
}

//スタートアップ
int _tmain(int argc,_TCHAR **argv)
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  
  /*------------------------------------------------------------
    スレッドハンドル、パイプ、スレッドに渡すコンテキスト
    配列はそれぞれ、0:標準入力用 1:標準出力用 2:標準エラー出力用
  --------------------------------------------------------------*/
  HANDLE hThreads[3] = {NULL};
  Pipe Std[3] = {PIPE_READ_INHERIT,PIPE_WRITE_INHERIT,PIPE_WRITE_INHERIT};
  Context ctx[3];

  //ログを書き込むファイル
  HANDLE hFile = NULL;
  
  //CreateProcessに必要な構造体
  STARTUPINFO si = {sizeof(STARTUPINFO)};
  PROCESS_INFORMATION pi = {NULL};

  //クリティカルセクション初期化
  InitializeCriticalSectionAndSpinCount(&Context::CriticalSection,SPIN_COUNT);

  if((hFile = OpenLogFile(LOGFILE)) == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("ログファイルのオープンが失敗しました"));
      goto cleanup;
    }

  //入出力ハンドルをリダイレクトしたコマンドプロンプトを生成
  si.dwFlags    = STARTF_USESTDHANDLES;
  si.hStdInput  = Std[0].hRead;   //パイプにリダイレクト
  si.hStdOutput = Std[1].hWrite; //パイプにリダイレクト
  si.hStdError  = Std[2].hWrite; //パイプにリダイレクト

  if(FALSE == CreateCommandPrompt(si,pi))
    {	
      _tprintf(TEXT("CreateProcess 失敗 : %d"),GetLastError());
      goto cleanup;
    }

  //コンテキスト構造体のメンバをセット
  ctx[0].Set(GetStdHandle(STD_INPUT_HANDLE),Std[0].hWrite);
  ctx[1].Set(Std[1].hRead,GetStdHandle(STD_OUTPUT_HANDLE),hFile);
  ctx[2].Set(Std[2].hRead,GetStdHandle(STD_ERROR_HANDLE),hFile);
  
  //スレッド作成
  for(int i=0;i<3;i++)
    hThreads[i] = CreateThread(NULL,0,ReadAndWriteProc,&ctx[i],0,NULL);
  
  //新しいコマンドプロンプトが終了するまで待機
  WaitForSingleObject(pi.hProcess,INFINITE);
  
  //スレッド終了通知をセット
  Context::bThreadMustTerminate = TRUE;

  //パイプハンドルのクローズ
  for(int i=0;i<3;i++)
    Std[i].Close();

  CloseHandle(GetStdHandle(STD_INPUT_HANDLE));

  //起動したスレッドが終了するまで待機
  WaitForMultipleObjects(3,hThreads,TRUE,INFINITE);

  //クリティカルセクションオブジェクトを削除
  DeleteCriticalSection(&Context::CriticalSection);

cleanup:
  //ハンドルを閉じる
  CLOSEHANDLE(hFile);
  CLOSEHANDLE(pi.hThread);
  
  for(int i=0;i<3;i++)
    CLOSEHANDLE(hThreads[i]);
  
  CLOSEHANDLE(pi.hProcess);

  return 0;
}

すべてのコードを固めたものはこちら。
全てのソースはこっちに移動
nmakeでcmdlog.exeが生成されます。

ただ、これは CTRL-Cとかすると・・・コマンドプロンプト自体がガサッと終了してしまいますし、入力した内容がエコーされるし(ファイルには出力されないので別に良いのですが・・・)、まだまだ不完全。
書き出すファイルパスはハードコードしとるし、エラー処理していないし、ファイルの書き出しは同期処理なので、遅いデバイスを指定すると(今更ないと思うけどフロッピーとか)書出処理から制御が戻ってこないので非常にマズイことになってます・・・。

また暇なときに完成度を高めたい・・・といっててそのままでずっと使うことが多いんですが(ーー;


コンソール(CMD.EXE)のログを取る (不完全版)
コマンドプロンプトのログを取る(その2)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る(完結)

コンソール(CMD.EXE)のログを取る (不完全版)

コマンドプロンプト(CMD.EXE)の操作や出力ログを全部取りたい、と思っては見たものの、Windows標準の機能だけでは完全なログを取ることができません。リダイレクトしても入力したログが記録されないし、何より、コンソール画面に出力されないので入力しつつ、出力も表示しながら、かつすべてのログをとる、ということが標準でできません。

確かUnix系のOSなら、teeコマンドというものでできるらしいのですが・・・Windowsにはありません。そういう需要がWindowsには少ないのか、ネットで探すと、VBSスクリプトで実装しているもの(Tee.vbs)もありますが・・・。

これが Cygwin であれば、TeratermやPedorosaのようなターミナルエミュレータでCygwinに接続することですべてのログを取ることが可能ですが、残念ながらこれらのターミナルからCMD.EXEには接続できません。

というわけで、C++で強引に作ってみよう、というわけで、とりあえず動作チェック・テスト用コードを書いてみました。

仕組み自体は、CreateProcessで標準入出力をリダイレクトしたCMD.EXEのプロセスを作って、匿名パイプで出力を取得してファイルと画面に出力する、という至って普通のやり方です。

なんとか強引にコーディングして、標準出力と標準エラー出力はファイルに落とすことはできましたが、まだ入力のログがとれてません。また来週にでも完全版を書きたいと思ってます。。。

/****************************************************************

  ■CMD.EXE のログをとる不完全版テストコード
     (入力がログに落ちません)

****************************************************************/

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

#define CLOSEHANDLE(X) {if(X)CloseHandle(X);}
#define SAFEDELETE(X) {if(X)delete (X);}
#define BUFFER_SIZE 1024
#define SPIN_COUNT 4000
#define LOGFILE_PATH TEXT("cmdlog.log")

//匿名パイプをラップしたもの
struct PipeHandle
{
  HANDLE hRead;
  HANDLE hWrite;
  SECURITY_ATTRIBUTES sa;

  PipeHandle() : hRead(NULL),hWrite(NULL)
    {
      sa.nLength = sizeof(SECURITY_ATTRIBUTES);
      sa.lpSecurityDescriptor = NULL;
      sa.bInheritHandle = TRUE;

      ::CreatePipe(&hRead,&hWrite,&sa,0);
    }
  ~PipeHandle()
    {
      CLOSEHANDLE(hWrite);
      CLOSEHANDLE(hRead);
    }
};

//スレッド関数に各種変数を渡すために使用するコンテキスト
struct Context
{
  PipeHandle *lpStdHandle;
  HANDLE hFile;
  HANDLE hStdio;
  LPCRITICAL_SECTION lpCriticalSection;
  PBOOL pThreadMustTerminate;

  Context(LPCRITICAL_SECTION lpcs,PBOOL ptht) 
    : lpCriticalSection(lpcs),pThreadMustTerminate(ptht)
    {}
};

//パイプから出力を読み取るスレッド関数
DWORD WINAPI ReadOutputProc(LPVOID lpParameter)
{
  Context *context = (Context*)lpParameter;

  TCHAR pBuffer[BUFFER_SIZE] = {0};
  DWORD dwRead = 0,dwWrite = 0;

  while(!(*context->pThreadMustTerminate))
    {
      BOOL b = FALSE;
      DWORD dwReadAvail = 0;
      do
        {
          b = ReadFile(context->lpStdHandle->hRead,(PVOID)pBuffer,BUFFER_SIZE,&dwRead,NULL);
          if(dwRead > 0)
            {
              EnterCriticalSection(context->lpCriticalSection);
              WriteFile(context->hFile,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              WriteFile(context->hStdio,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              LeaveCriticalSection(context->lpCriticalSection);
            }
          if(*context->pThreadMustTerminate == TRUE)
            break;
        }
      while(b == TRUE && dwRead > 0);
    }

  return 0;
}

//スタートアップ
int _tmain(int argc,_TCHAR **argv)
{
  //CMD.EXEのパスを格納するバッファ
  TCHAR szCmdPath[MAX_PATH+1] = {0};
  
  //ログを書き込むファイル
  HANDLE hFile = NULL;
  
  //CreateProcessに必要な構造体
  STARTUPINFO si;
  PROCESS_INFORMATION pi;

  //パイプ
  PipeHandle *ppStdOut = new PipeHandle();
  PipeHandle *ppStdErr = new PipeHandle();

  //スレッドの同期処理に必要
  CRITICAL_SECTION cs;
  BOOL bThreadMustTerminate = FALSE;

  //標準入力コンソールハンドル
  HANDLE hStdInput = GetStdHandle(STD_INPUT_HANDLE);
  
  //スレッドに渡すコンテキスト
  Context ctxOut(&cs,&bThreadMustTerminate);
  Context ctxErr(&cs,&bThreadMustTerminate);

  //クリティカルセクションオブジェクト初期化
  InitializeCriticalSectionAndSpinCount(&cs,SPIN_COUNT);

  //CMD.EXEのパスを環境(システム)変数から得る。
  if(0 == GetEnvironmentVariable(TEXT("COMSPEC"),szCmdPath,MAX_PATH+1))
    {
      _tprintf(TEXT("環境変数 COMSPEC 取得失敗\n"));
      return 0;
    }

  //ログファイルをオープン
  if((hFile = CreateFile(LOGFILE_PATH,
                         GENERIC_WRITE,
                         0,
                         NULL,
                         CREATE_ALWAYS,
                         FILE_ATTRIBUTE_NORMAL,
                         NULL)) == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("CreateFile が失敗しました"));
      goto cleanup;
    }
  

  // CMD.EXEのプロセスを生成
  ZeroMemory((LPVOID)&si,sizeof(STARTUPINFO));
  si.cb = sizeof(STARTUPINFO);
  si.dwFlags    = STARTF_USESTDHANDLES;
  si.hStdInput  = hStdInput;
  si.hStdOutput = ppStdOut->hWrite; //パイプにリダイレクト
  si.hStdError  = ppStdErr->hWrite; //パイプにリダイレクト

  if(FALSE == CreateProcess(NULL,
                            szCmdPath,
                            NULL,
                            NULL,
                            TRUE,
                            NORMAL_PRIORITY_CLASS,
                            NULL,
                            NULL,
                            &si,
                            &pi))
    {	
      _tprintf(TEXT("CreateProcess 失敗 : %d"),GetLastError());
      goto cleanup;
    }
  
  //スレッドに渡すコンテキストのメンバをセット
  ctxOut.lpStdHandle = ppStdOut;
  ctxOut.hFile = hFile;
  ctxOut.hStdio = GetStdHandle(STD_OUTPUT_HANDLE);
  
  ctxErr.lpStdHandle = ppStdErr;
  ctxErr.hFile = hFile;
  ctxErr.hStdio = GetStdHandle(STD_ERROR_HANDLE);

  /* スレッド作成 */
  HANDLE hThread1 = CreateThread(NULL,0,ReadOutputProc,&ctxOut,0,NULL);
  HANDLE hThread2 = CreateThread(NULL,0,ReadOutputProc,&ctxErr,0,NULL);

  //CMDプロセス終了まで待機
  WaitForSingleObject(pi.hProcess,INFINITE);
  
  //スレッド終了通知
  bThreadMustTerminate = TRUE;

  //パイプの削除
  SAFEDELETE(ppStdOut);
  SAFEDELETE(ppStdErr);
  
  //クリティカルセクションオブジェクトを削除
  DeleteCriticalSection(&cs);

cleanup:
  //ハンドルを閉じる
  CLOSEHANDLE(hFile);
  CLOSEHANDLE(pi.hThread);
  CLOSEHANDLE(pi.hProcess);
  CLOSEHANDLE(hThread1);
  CLOSEHANDLE(hThread2);

  return 0;
}

なんか・・・汚いコードだな、相変わらず・・・。
これだけなら、

CMD.EXE 2>&1 |  xxxxxx

と変わらないなぁ・・・。


コマンドプロンプトのログを取る(完結)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る (その2)

ヒープとshared_ptr

先日ファイルシステムが壊れてます、みたいなエラーが記録されているのを発見。
ググったらデフラグかけたらエラーがなくなった、みたいな記事を見つけました。

いくらなんでもそれはないだろう・・・と思ったけど・・・、
まぁ、エントロピーが増大しまくっているHDDを久しぶりにデフラグをかけたら
ちょっとは速くなるだろうと思い3時間かけてデフラグ終了。
エラーが記録されているHDDにデフラグをかけるなんて、傷に塩を塗り込む行為だとは思いつつ(^^;

で、今日イベントビューア見たらエラーが消えてました・・・どーなってんの、コレ?(笑)
まぁいいや。

で、本題です。

Windowsで動的にメモリブロックを確保するには、Heap APIを使うか、もっと原始的に?VirtualAlloc()などの仮想メモリを直接確保するAPIを駆使するか・・・と思います。でも仮想メモリを直接いじると何でもできちゃう反面、ちょっと大げさですし・・・。ということで、よほど大きなメモリブロックを確保するほどでもないサイズだとヒープAPIを使用するほうがお手軽です。

そんなの malloc()で十分・・・という感じもしますが・・・ちょっとした備忘録のメモも兼ねています。

で、確保したメモリブロックの寿命管理をスマートポインタ、shared_ptr に任せてしまおう、というわけです。

プライベートヒープを使用する上で、HeapCreate()とHeapDestroy()を呼ばないといけないわけで、ヒープハンドルをいつ廃棄するか(HeapDestroy()を呼ぶか)を参照カウンタで管理したいと思い次のようなコードを思いつきました。

//-----shared_heap.h-------

#include <windows.h>

//shared_ptrはboost版でもいいですが、
//Visual C++ 2008SP1にはTR1の実装があるのでそれを使います。
#include <memory>

using namespace std::tr1;

class shared_heap
{
public:
  typedef std::tr1::shared_ptr<VOID> return_type;

private:
	//CCriticalSectionは CRITICAL_SECTION構造体を単純にラップしたクラスです。
  static CCriticalSection _criticalsection;
  static HANDLE _sHeap;
  static UINT _sCount;
  
  class HeapDeleter
    {
    public:
      void operator()(PVOID ptr)
        {
          _criticalsection.Enter();
          if(::HeapFree(_sHeap,HEAP_NO_SERIALIZE,ptr))
            {
              if((--_sCount == 0) && _sHeap)
                {
                  ::HeapDestroy(_sHeap);
                  _sHeap = NULL;
                }
            }
          _criticalsection.Leave();
        }
    };
  
  shared_heap(){};
  ~shared_heap(){};

public:
  static return_type alloc(size_t size)
    {
      return_type retVal;
      
      _criticalsection.Enter();
      if(!_sHeap)
        _sHeap = ::HeapCreate(HEAP_NO_SERIALIZE,0,0);
      
      retVal = return_type(::HeapAlloc(_sHeap,HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY,size),HeapDeleter());
      _sCount++;
      
      _criticalsection.Leave();
      
      return retVal;
    }
};

CCriticalSection shared_heap::_criticalsection;
HANDLE shared_heap::_sHeap  = NULL;
UINT   shared_heap::_sCount = 0;

この shared_heapクラスは単純に静的関数を集めたもので、インスタンス化しても意味はないのでコンストラクタとデストラクタはプライベートにしています。実際には、各静的メンバ関数およびカスタムデリータの定義(.h)と実装(.cpp)を分ける必要がありますし、静的メンバ変数の初期化は実装側(.cpp)に書く必要もありますねぇ・・・。

で、まぁ、要点はshared_heap::alloc()で、HeapAlloc()APIをコールして得たメモリブロックのポインタをカスタムデリータとともにshared_ptrのコンストラクタに渡して、shared_ptrを返しています。スレッドセーフにするためクリティカルセクションを使用しています。

非常に単純なコードですが、これで廃棄処理コードを書く手間が省けます。shared_ptr 万歳!(^_^;;;

//使用例
#include "shared_heap.h"
int _tmain(int argc,_TCHAR **argv)
{
  shared_heap::return_type sptr = shared_heap::alloc(512);

  //なんかの処理

  return 0;
}

※僕の備忘録ですので、多々間違いがあるかもしれません。誰もいないと思いますが、そのままをコピペして使用するのは危険です。ご了承を。

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

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

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