カスタム分類のrewrite

私的備忘録。

カスタム分類(Taxonomy)を登録する際、rewrite 引数をtrueにしたとき、has_archiveをtrueにしているにも関わらず、一覧が表示されないことがあった。あれこれ設定を調べてみても原因らしきものが分からなかった。

1日ほど、あれやこれや試した結果、どうやら、設定 => パーマリンク設定で、「変更を保存」を行わないとカスタム分類のrewriteが有効にならないことが判明。

もっと、厳密に調べてみると、wordpress api、flush_rewrite_rules関数をrewriteルールを変更した場合に最低一回コールしなければならない、らしい。

毎回コールする必要がないらしい?(実際のところ、面倒なのでソースを追ってないので理屈は分かりません) ので、設定・パーマリンク設定の変更を保存ボタンをクリックすることで、flush_rewrite_rules関数がコールされるのと同じことがされるみたい。

開発段階で、rewrite関係でおかしくなったら、パーマリンク設定で「変更を保存」で回避。

なんか、やっぱり本一冊買ってイチから勉強しなおした方が効率がいいかもしれない。。。

TortoiseSVN 1.8.0 でクライアント認証ができず・・・

Subversionが1.8にアップデートしたので、TortoiseSVN 1.8.0を何も考えずクライアントPCにインストールした。作業コピーに変更があったみたいで、作業コピーを片っ端からアップデートアップグレードした。

・・・結果、「TortoiseSVN 1.7.7でクライアント認証が失敗する」の二の舞を踏むことに・・・orz

インストールする時一抹の不安が過ぎったけど、後先考えず、「エイヤ!」ってな感じで。結局、やっぱり失敗。

一応サーバーはCentOS 5でApache 2.2.3 + オレオレ認証局のSSL環境。TortoiseSVN1.7.12を使っていたときはちゃんとクライアント認証のプロセスが通ったが、またしても1.8.0に上げたら今度は固まったまま。subversion自体がダメなのか、それともTortoiseSVNの部分がダメなのか、調べるだけの知識がないので、もうお手上げ。

検索してみたけど、やっぱりリリース直後なのでバージョン1.8の情報が出てこない。

結局、Apacheの設定で、SSLクライアント認証を解除して、ダイジェスト認証に切り替えることで、事なきを得た。

・・・が、Windows7上でTortoiseSVN1.8.0をインストールすると、ダイジェスト認証すらなんか怪しい挙動をする。作業コピーの更新をすると、ユーザー・パースワード入力ダイアログが出るのですが、正しいユーザー名・パスワードを入れてもエラーになる、あきらめてキャンセルすると、今度は違うダイアログ(Windows7の資格情報のダイアログ?)が出てくる。なんかワケ分からない。

ダメもとで何回もユーザー・パスワードの入力をトライしてみると、認証された。

よく分からん。

とにかく、元に戻そうかと・・・思ったけど、十数個ある作業コピーのチェックアウト作業がメンドクサイので、Apacheの設定をこのままにして、バージョンアップを待つことにする。

サーバーがオレオレSSLだからダメなのかなー・・・・とか、CentOS5のApacheのバージョンが低いのが原因なのかなー・・・とか思ったけど、確かめようがない。

この際だから、サーバー上のSubversionのバージョンを1.6系列から1.7系列最新の1.7.10にソースからビルドしてアップデートした。念のため、レポジトリのダンプをとったが、こちらは何ら問題なくバージョンアップ完了。さすがにサーバー上のsubversionを1.8.0にする気がしない。。。

テーマやテンプレートを動的に変更

私的記録です。

WordPressで、サイトの「お知らせ」というカスタム投稿タイプを作成した時のこと。

たとえば他のサイトからインラインフレームで読み込ませるときは、特定のテンプレートに変更したい、という場合、template_includeというフィルターが使える。トリガーとして、URLパラメータになにがしらのパラメータを渡したときに切り替えるようにすれば上手くいきます。

また、ある特定のURLパラメータをつけてアクセスしたときは、別のテーマで表示したい! という、それって何の意味があるの?的なこともしたかったので、纏めてプラグインにした。

チョー簡単(^^; WordPressってホントよく出来てますね~。

<?php
/*
Plugin Name: Switcher for Theme or Template
*/

/************************************************************
 動的にテーマやテンプレートを変える
 すっごい手抜きサンプル
************************************************************/
add_filter('template_include','DynaChange::Template');
add_filter('stylesheet', 'DynaChange::Theme');
add_filter('template', 'DynaChange::Theme');

class DynaChange
{
  public static function Template($template)
    {
      //URLパラメータにchtmp=プレフィックス があれば、
      //現在のテンプレートファイル名にプレフィックスを付けた
      //テンプレートファイルに切り替える。
      //適当サンプルのため、子テーマには対応していない。
      if(!empty($_GET['chtmp']))
        {
          $name = str_replace(STYLESHEETPATH . '/', '', $template);
          $template = STYLESHEETPATH . '/' . $_GET['chtmp'] .'-'. $name;
        }

      return $template;
    }

  public static function Theme($stylesheet)
    {
      //URLパラメータに chth=テーマ名 があれば、
      //そのテーマに切り替える。

      if(!empty($_GET['chth']))
        {
          $theme = wp_get_theme($_GET['chth']);
          if($theme->exists())
            $stylesheet = $theme->Template;
        }
      return $stylesheet;
    }
}
?>

説明要らないっすね~。

Vista以降では安易にスレッドを作るな!?

LinuxやBSDなどのUNIX互換のOSと違って、Windowsではスレッドを起こすのは普通に行われてることだと思います。で、スレッドを起こすには、CreateThread API もしくは、Cランタイムライブラリの_beginthreadex関数を使うことになります。

が、一方でスレッドを生成するのではなく、スレッドプールを利用する、というケースもあり、パフォーマンス的な事を考えれば、むしろこちらの方が多いかもしれません。

Windows 2000/XPなどのNT5.x系では、QueueUserWorkItem APIというスレッドプールに関するAPIが使えます。。。けど、はっきりいって、このAPI、よほど簡単なロジックでないと使えません。いったんこのAPIでキューに入れてしまうと、もう手が出ません。外からキャンセルできないし、強制終了させようとしてもTerminateThread APIは使っちゃだめ!ですし・・・。
結局、ちょっと凝ったことさせようと思ったら、自前でI/O完了ポートを駆使してスレッドプールを実装する他なかったと思います。

しかし、Windows Vista以降のNT6.x系のWindowsでは、より進化したスレッドプールAPIが追加されています。もはや、Vista以降のWindowsで、CreateThreadや_beginthreadex関数を使ってスレッドを起こすコーディングはダサイといわざる得ません(^^;;;

というか、CPUがデュアルコアは当たり前、マルチコアがデフォルトってな状況の中で、これからはメニーコアだ! っていう時代なので、アプリケーション開発者側が、コア数のことまで考えて組む・・・とかもう無理!そんなのは限られたスーパープログラマーしかできんわ(笑) ってことです。

だから、CreateThread APIや_beginthreadex関数でスレッドを起こす前提のロジックでコーディングしちゃだめ。ってことに。

ただ・・・スレッドを作ってそこにウィンドウを作成する、というようなケースには向きません。とどのつまり、プロセス開始から終了まで生存するようなスレッドの場合、スレッドプールに処理を投げる意味はありません。あくまで、一つのスレッドの生成・削除が頻繁に行われるケースや、スレッドの生存期間が短いケース等でスレッドプールの恩恵を受けることができると思います。

ま、今更ですが(^^;;; この新たに追加されたスレッドプールなAPI群は、スレッドの生成と管理をすべてWindowsが肩代わりしてマシンに積まれているCPUの数(コア数?)と負荷状態に応じて最適な性能を発揮できるようになっているようです(・・・なっているはずです)。

例えば、ちょっとしたユーティリティアプリなんかで、単純な処理を非同期(並行)処理させる場合、TrySubmitThreadpoolCallbackもしくは、CreateThreadpoolWork/SubmitThreadpoolWorkでほとんど事足りると思います。

CreateThreadや_beginthreadexのあの引数の多さにうんざりすることが無くなり、スレッドハンドルを管理するコード、同期のためにイベントオブジェクトを作成して待機するようなコード諸々を実装する手間が大幅に軽減されます。ヒャッホーーー!

/********************************************************
  簡単なスレッドプールサンプル
********************************************************/
#pragma comment(lib,"comsuppw.lib")
#define _WIN32_WINNT 0x0600

#include <windows.h>
#include <tchar.h>
#include <cstdio>
#include <comutil.h>

using namespace std;

#define NUM 10

/********************************************************
クリティカルセクションのラッパークラス
********************************************************/
class CCriticalSection
{
  CRITICAL_SECTION cs;

public:
  CCriticalSection(){ InitializeCriticalSectionAndSpinCount(&cs,4000); }
  ~CCriticalSection(){ DeleteCriticalSection(&cs); }

  void Enter(){ EnterCriticalSection(&cs); }
  void Leave(){ LeaveCriticalSection(&cs); }
};

/********************************************************
 スレッドに渡す何らかのデータ(適当です)
********************************************************/
struct Data : CCriticalSection
{
  int i;
  _bstr_t bs;
};

/********************************************************
  コールバック関数
********************************************************/
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE pInstance,LPVOID pvParam,PTP_WORK pWork)
{
  Data *pData = (Data *)pvParam;
  DWORD tid = GetCurrentThreadId();

  //なんらかの処理
  pData->Enter();
  {
    _tprintf(TEXT("[%06d][%02d] %s\n"),tid,pData->i++,(PCTSTR)pData->bs);
  }
  pData->Leave();
}

/********************************************************
  エントリポイント
********************************************************/
void main(void)
{
  PTP_WORK pWork;
  Data param;

  param.i = 0;
  param.bs = TEXT("なにがしらのデータ");

  pWork= CreateThreadpoolWork(WorkCallback,(PVOID)&amp;param,NULL);

  //スレッドプールにNUM回作業を投げる。
  for(int i=0;i<NUM;i++)
    {
      /*******************************************************
      スレッドプールのキューに送信

      SubmitThreadpoolWorkで同じPTP_WORKを繰り返す場合、
      コールバック関数に渡されるパラメータはCreateThreadpoolWorkをコールしたときの
      パラメータが渡されるのでその点注意しなければなりません。
      *******************************************************/
      SubmitThreadpoolWork(pWork);
    }

  //投げた作業が終了するまで待つ。
  WaitForThreadpoolWorkCallbacks(pWork,FALSE);
  
  //2013/6/18 追記 閉じるのを忘れてました。
  CloseThreadpoolWork(pWork);
}

当然ですが、上記はXPでは実行できません。

他にも、タイマーオブジェクトを利用した関数の繰り返し処理、jscriptでいうところのsetTimeout / setIntervalメソッドみたいな、一定時間毎にコールバック関数を実行してくれるCreateThreadpoolTimer APIや、カーネルオブジェクトがシグナル状態になったらコールバック関数を実行してくれる CreateThreadpoolWait API、ReadFile/WriteFileなどのI/O非同期処理に利用できるCreateThreadpoolIo API。

一連のスレッドプールに関するAPIは非常に強力で、使い勝手もいい。デフォルトのスレッドプールの動作が気に入らなければ、カスタマイズしたスレッドプールを利用することもできる。

自分的に便利だと思ったのが、ReadFIle/WriteFileでI/O非同期処理に利用できる、CreateThreadpoolIo/StartThreadpoolIo。
今まではI/O完了ポートで通知を受け取って・・・というようなコードを書いてましたが、Vista以降のOSに限定すれば、これらのスレッドプールAPIを使うことでコード量が減ります。

ReadDirectoryChangesW APIを使ったディレクトリへの変更を監視するコードをスレッドプールを使ったコードに強引にリプレースしてみました。

まずは、昔作った、WindowsXPで動作する、IO完了ポートとワーカースレッドを単純に作って利用したバージョン。

#pragma comment(lib,"comsuppw.lib")

#include <windows.h>
#include <process.h>
#include <conio.h>
#include <comutil.h>
#include <cstdio>
#include <cstring>
#include <tchar.h>

using namespace std;

//バッファサイズは適当です。
#define BUFFER_SIZE 1024
#define COMPKEY_QUIT -1
#define COMPKEY_DIR 1

//使用するデータをパックした構造体
typedef struct _IocpData : OVERLAPPED
{
  TCHAR szDir[MAX_PATH];
  HANDLE hDir;
  BYTE pvData[BUFFER_SIZE];  //FILE_NOTIFY_INFORMATION構造体に必要なメモリブロック
} IocpData,*PIOCPDATA;

//プロトタイプ
unsigned int __stdcall WatchDirectory(PVOID pvParam);
unsigned int __stdcall IoCompletionCallback(LPVOID pvParam);

HANDLE g_hQuitEvent = NULL;

int _tmain(int argc,TCHAR**argv)
{
  if(argc < 2)
    {
      _tprintf(TEXT("ディレクトリを指定してください。\n"));
      goto cleanup;
    }

  //待機終了通知のためのイベントオブジェクトを初期化
  g_hQuitEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
  
  HANDLE hThread = (HANDLE)_beginthreadex(NULL,0,WatchDirectory,(PVOID)argv[1],0,NULL);

  if(WAIT_TIMEOUT == WaitForSingleObject(g_hQuitEvent,1000))
    {
      while(TRUE)
        {
          if('q' == _gettch())
            {
              //終了通知を投げる。
              SetEvent(g_hQuitEvent);

              //スレッドが終了するまで待機
              WaitForSingleObject(hThread,INFINITE);

              break;
            }
        }
    }

  CloseHandle(hThread);
  CloseHandle(g_hQuitEvent);

cleanup:
  return 0;
}

/********************************************************
ディレクトリを監視
********************************************************/
unsigned int __stdcall WatchDirectory(PVOID pvParam)
{
  PCTSTR pDir = (PCTSTR)pvParam;

 IocpData ioReq;

  //ワーカースレッドはとりあえず2個。
  HANDLE hThreads[2] = {0};

  //FILE_FLAG_OVERLAPPEDを指定してディレクトリを開く。
  _tcscpy(ioReq.szDir,pDir);
  ioReq.hDir = CreateFile(pDir,
                          FILE_LIST_DIRECTORY,
                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                          NULL,
                          OPEN_EXISTING,
                          FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                          NULL);

  if(ioReq.hDir == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("ディレクトリのオープンに失敗しました。\n"));
      SetEvent(g_hQuitEvent);
      return 0;
    }

  //IO完了ポートの作成とディレクトリハンドルの関連付け
  HANDLE hIocp = CreateIoCompletionPort(ioReq.hDir,NULL,COMPKEY_DIR,0);
  if(hIocp == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("I/O完了ポートの作成に失敗しました。\n"));
      SetEvent(g_hQuitEvent);
      return 0;
    }

  //IO完了ポートを待機するワーカースレッドを起動
  //監視するディレクトリ一つなので、ワーカースレッドを二つも作る必要はないと思いますが・・・
  //IO完了ポートに複数の(ディレクトリの)ハンドルを関連付ける場合に複数のワーカースレッドを作る方がいいかも。
  for(int i=0;i<2;i++)
    hThreads[i] = (HANDLE)_beginthreadex(NULL,0,IoCompletionCallback,(PVOID)hIocp,0,NULL);

  _tprintf(TEXT("監視用タスクが待機状態に入ります。\n"));

  PostQueuedCompletionStatus(hIocp,0,COMPKEY_DIR,(LPOVERLAPPED)&ioReq);

  //終了イベントがシグナル状態になるまで待機する。
  WaitForSingleObject(g_hQuitEvent,INFINITE);

  for(int i=0;i<2;i++)
    PostQueuedCompletionStatus(hIocp,0,COMPKEY_QUIT,NULL);

  //ワーカースレッドが終了するまで待機
  WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);

  for(int i=0;i<2;i++)
    CloseHandle(hThreads[i]);
  
  _tprintf(TEXT("監視用タスクが待機状態から解放され終了します。\n"));

  CloseHandle(hIocp);
  return 1;
}

/*****************************************************************
 IO完了通知を受けるワーカースレッド関数
*****************************************************************/
unsigned int __stdcall IoCompletionCallback(PVOID pvParam)
{
  HANDLE hIocp = (HANDLE)pvParam;

  DWORD dwBytes = 0L;
  ULONG_PTR ulCompKey = 0L;
  LPOVERLAPPED pOverlapped = NULL;
  IocpData *pIoReq = NULL;

  while(TRUE)
    {
      GetQueuedCompletionStatus(hIocp,&dwBytes,&ulCompKey,&pOverlapped,INFINITE);
      PFILE_NOTIFY_INFORMATION pFileNotifyInfo = NULL;
      pIoReq = (IocpData*)pOverlapped;

      if(ulCompKey == (ULONG_PTR)-1)
        break;

      //FILE_NOTIFY_INFORMATION構造体をコピーする。
      if(dwBytes > 0)
        {
          PFILE_NOTIFY_INFORMATION pfni = (PFILE_NOTIFY_INFORMATION)pIoReq->pvData;
          SIZE_T cb = sizeof(FILE_NOTIFY_INFORMATION) + pfni->FileNameLength;
          
          pFileNotifyInfo = (PFILE_NOTIFY_INFORMATION)new BYTE[cb];
          CopyMemory((PVOID)pFileNotifyInfo,(CONST PVOID)pfni,cb);
        }

      //次の監視を開始
      if(pOverlapped)
        ReadDirectoryChangesW(pIoReq->hDir,
                              (PVOID)pIoReq->pvData,
                              BUFFER_SIZE,
                              TRUE,
                              FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME,
                              NULL,
                              pOverlapped,
                              NULL);

      /**************************************************************************************
      コールバック関数に渡すNotifyParams構造体に値をセットしてコールバック関数をコールする。
      
      ※コールバック関数が制御を返さないかぎりスレッドがワーカースレッドがビジーになる。
      **************************************************************************************/
      if(dwBytes > 0)
        {
          _bstr_t filename,fullpath;

          //FILE_NOTIFY_INFORMATION構造体のFileNameメンバの終端をヌル文字で閉じる。
          *(PWSTR)((PBYTE)(pFileNotifyInfo->FileName) + pFileNotifyInfo->FileNameLength) = L'&#092;&#048;';

          //変更のあったファイルのフルパスを得る。
          filename = pFileNotifyInfo->FileName;
          fullpath = pIoReq->szDir;
          fullpath += (_bstr_t(TEXT("\\")) + filename);

          _tprintf(TEXT("[THREAD:%d] [変更]%s\n"),GetCurrentThreadId(),(PCTSTR)fullpath);

          //確保したメモリを解放
          delete [] (PBYTE)pFileNotifyInfo;
        }
    }

  return 1;
}

要点は、スレッドを作成して監視が終了するまで待機。ReadDirectoryChangeW APIの非同期処理が完了するとIO完了ポートのキューにI/O完了パケットが追加され、ワーカースレッドのGetQueuedCompletionStatus APIが制御を戻すことでReadDirectoryChangeW APIの処理結果のデータを得て、再びReadDirectoryChangeW APIをコールし非同期処理を継続します。

続いて、上記をVista以降のスレッドプールのAPIを利用したバージョン。

#pragma comment(lib,"comsuppw.lib")

#include <windows.h>
#include <comutil.h>
#include <cstdio>
#include <cstring>
#include <tchar.h>
#include <conio.h>

using namespace std;

//バッファサイズは適当です。
#define BUFFER_SIZE 1024

//使用するデータをパックした構造体
typedef struct _IocpData : OVERLAPPED
{
  TCHAR szDir[MAX_PATH];
  HANDLE hDir;
  BYTE pvData[BUFFER_SIZE];  //FILE_NOTIFY_INFORMATION構造体に必要なメモリブロック
} IocpData,*PIOCPDATA;

//プロトタイプ
VOID CALLBACK WatchDirectoryEx(PTP_CALLBACK_INSTANCE pInstance,LPVOID pvParam,PTP_WORK pWork);
VOID CALLBACK IoCompletionCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID pvParam,PVOID pOverlapped,ULONG IoResult,ULONG_PTR ulBytes,PTP_IO pio);

HANDLE g_hQuitEvent = NULL;

int _tmain(int argc,TCHAR**argv)
{
  if(argc < 2)
    {
      _tprintf(TEXT("ディレクトリを指定してください。\n"));
      goto cleanup;
    }

  //待機終了通知のためのイベントオブジェクトを初期化
  g_hQuitEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

  //作業を作成
  PTP_WORK pWork = CreateThreadpoolWork(WatchDirectoryEx,(PVOID)argv[1],NULL);

  //スレッドプールに作業を投げる。
  SubmitThreadpoolWork(pWork);

  if(WAIT_TIMEOUT == WaitForSingleObject(g_hQuitEvent,1000))
    {
      while(TRUE)
        {
          if('q' == _gettch())
            {
              //終了通知を投げる。
              SetEvent(g_hQuitEvent);
              
              //投げた作業が終了するまで待つ。
              WaitForThreadpoolWorkCallbacks(pWork,FALSE);
              
              break;
            }
        }
    }

cleanup:
  return 0;
}

/********************************************************
ディレクトリを監視
********************************************************/
VOID CALLBACK WatchDirectoryEx(PTP_CALLBACK_INSTANCE pInstance,LPVOID pvParam,PTP_WORK pWork)
{
  PCTSTR pDir = (PCTSTR)pvParam;
  IocpData ioReq;

  //FILE_FLAG_OVERLAPPEDを指定してディレクトリを開く。
  _tcscpy(ioReq.szDir,pDir);
  ioReq.hDir = CreateFile(pDir,
                          FILE_LIST_DIRECTORY,
                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                          NULL,
                          OPEN_EXISTING,
                          FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                          NULL);

  if(ioReq.hDir == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("ディレクトリのオープンに失敗しました。\n"));
      SetEvent(g_hQuitEvent);
      return;
    }

  PTP_IO pio = CreateThreadpoolIo(ioReq.hDir,IoCompletionCallback,(PVOID)&ioReq,NULL);

  _tprintf(TEXT("監視用タスクが待機状態に入ります。\n"));
  
  //コールバック関数をコールしてディレクトリ監視を開始。
  IoCompletionCallback(NULL,(PVOID)&ioReq,(LPOVERLAPPED)&ioReq,NO_ERROR,0,pio);

  //終了イベントがシグナル状態になるまで待機する。
  WaitForSingleObject(g_hQuitEvent,INFINITE);

  //現在キューに残っている作業をキャンセルする。
  WaitForThreadpoolIoCallbacks(pio,TRUE);

  //ハンドルを閉じる。
  CloseHandle(ioReq.hDir);
  CloseThreadpoolIo(pio);

  _tprintf(TEXT("監視用タスクが待機状態から解放され終了します。\n"));
}

/*****************************************************************
 IO完了コールバック関数
*****************************************************************/
VOID CALLBACK IoCompletionCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID pvParam,PVOID pOverlapped,ULONG IoResult,ULONG_PTR ulBytes,PTP_IO pio)
{
  PFILE_NOTIFY_INFORMATION pFileNotifyInfo;
  IocpData *pIoReq = (IocpData*)pOverlapped;

  //転送されたバイト数が0以上なら、
  //FILE_NOTIFY_INFORMATION構造体のメモリを確保してコピーしておく。
  if(ulBytes > 0)
    {
      //転送されたFILE_NOTIFY_INFORMATION構造体をコピーする。
      PFILE_NOTIFY_INFORMATION pfni = (PFILE_NOTIFY_INFORMATION)pIoReq->pvData;
      SIZE_T cb = sizeof(FILE_NOTIFY_INFORMATION) + pfni->FileNameLength;

      pFileNotifyInfo = (PFILE_NOTIFY_INFORMATION)new BYTE[cb];
      CopyMemory((PVOID)pFileNotifyInfo,(CONST PVOID)pfni,cb);
    }

  //監視続行
  if(pOverlapped)
    {
      //スレッドプールにIO完了オブジェクトを関連付ける
      StartThreadpoolIo(pio);

      //ディレクトリ監視を開始するが、非同期なので制御が直に返る。
      ReadDirectoryChangesW(pIoReq->hDir,
                            pIoReq->pvData,
                            BUFFER_SIZE,
                            TRUE,
                            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME,
                            NULL,
                            (LPOVERLAPPED)pOverlapped,
                            NULL);
    }

  //転送されたバイト数が0以上なら、何らかの処理をする。
  //ここでは変更のあったファイルを出力する。
  if(ulBytes > 0)
    {
      _bstr_t filename,fullpath;

      //FILE_NOTIFY_INFORMATION構造体のFileNameメンバの終端をヌル文字で閉じる。
      *(PWSTR)((PBYTE)(pFileNotifyInfo->FileName) + pFileNotifyInfo->FileNameLength) = L'&#092;&#048;';

      //変更のあったファイルのフルパスを得る。
      filename = pFileNotifyInfo->FileName;
      fullpath = pIoReq->szDir;
      fullpath += (_bstr_t(TEXT("\\")) + filename);

      _tprintf(TEXT("[THREAD:%d] [変更]%s\n"),GetCurrentThreadId(),(PCTSTR)fullpath);

      //確保したメモリを解放
      delete [] (PBYTE)pFileNotifyInfo;
    }
}

あまり違いがないように思いますが、スレッドを何個作るべき?だとか細々とした調整などが不要になり、何より_beginthread関数などのプリミティブなAPIを使わなくても良くなりました。以前なら、スレッド処理をラップする、なんらかのクラスライブラリが必須だったと思いますが、ちょっとしたツールを書くときはこれらの新しいスレッドプールなAPIを使えば良くなりました。

ま、ちょっとしたツールを作るにはC#を使えば済む話で、わざわざC++を使う必要性があるとは思えませんが・・・。ま、要するに自己満です(^^;;;