WinAPIの困った(?)仕様 -サウンドプログラム編?-

 前から知っていたけど、どうせなのでまとめてみる。




timeSetEvent()

 一定時間ごとにスレッドを走らせる(もしくはシグナルを送る)のに使うタイマを設定する関数。

 >第一引数

   … 第二引数以下の数値を入れても意味が無い

     (第二引数は「タイマの精度」なので当たり前だけど)

   … 0を入れると失敗する。

     →大抵のタイマの精度は「1〜」になっているため、

      「0」は範囲外になるので、失敗する(…んだよね多分(汗))

      (制度に関しては「timeGetDevCaps()」あたりで調べる)

       このため、普通に使うときは

      タイマを設定した直後にスレッドは走らない*1

       即座に実行したい場合は、

      この関数の実行直後にシグナルを送るか

      スレッドにゴーサインを送るかしよう。

 >第三引数

   … 第五引数に

     「TIME_CALLBACK_EVENT_PULSE」や

     「TIME_CALLBACK_EVENT_SET」を潜ませてあり、

     尚且つコンパイラの警告レベルが最高の場合のみの話だけど、

     シグナルを送りたいイベントをそのまま書くと、

     「型が違う」

     と警告が出て、

     (LPTIMECALLBACK)でキャストしても、

     「サイズが違うのでキャストが上手くいかないかも」

     と警告が出る。

     (…現状、どうやってもここの警告は消えない?)

      とはいえ、リファレンス通りの使い方のはずなので、

     気にしなくても(多分)大丈夫…。





・スレッドに送るイベント

 上記の第三引数の所と同様の条件下での話。

  なぜか、


// 回したいスレッド
DWORD CALLBACK ThreadFunction( void *pcontext )
{
MSG msg;
DWORD signal = 0;
int loop = 1;

// シグナル感知用のイベント
HANDLE event = (HANDLE)pcontext;
...
...
// メッセージ用キュー作成
PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );

while( loop == 1 )
{
// シグナルが来るまで待つ
signal = MsgWaitForMultipleObjects( 1, event,
FALSE, INFINITE,
QS_ALLEVENTS );

// 通常処理
if( signal == WAIT_OBJECT_0 )
{
...
}
// 終了処理
else if( signal == WAIT_OBJECT_0 + 1 )
{
while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
Write_stdout( "end / playing_thread" );

loop = 0;
}
}
}
}

Write_stdout( "playing_thread / thread end" );
return 0;
}
...

...
{
// イベント生成用の文字列
TCHAR str[] = "PlayingThread";

event = CreateEvent( NULL, FALSE, FALSE, str );
thread = CreateThread( NULL, 0, ThreadFunction,
(void*)(event),
0, &threadID );
timeSetEvent( delay, span,
(LPTIMECALLBACK)(event), 0,
TIME_PERIODIC | TIME_CALLBACK_EVENT_PULSE );
}...
終了処理等は省略。

こんな感じでタイマをセットしてもスレッドにシグナルは来ません。

…(void*)でキャストしたのを戻してるだけだから大丈夫なはずなのに…。



 正解(?)はコチラ。


// 使いやすいようにまとめてみる
typedef struct struct_THREAD_MANAGER
{
HANDLE event,
thread;
DWORD threadID;

MMRESULT timerID;
} THREAD_MANAGER;

...

// 回したいスレッド
DWORD CALLBACK ThreadFunction( void *pcontext )
{
MSG msg;
DWORD signal = 0;
int loop = 1;

THREAD_MANAGER *thread = (THREAD_MANAGER*)pcontext;
// シグナル感知用のイベント
HANDLE *event = &(thread->event);
...
...
// メッセージ用キュー作成
PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );

while( loop == 1 )
{
// シグナルが来るまで待つ
signal = MsgWaitForMultipleObjects( 1, event,
FALSE, INFINITE,
QS_ALLEVENTS );

// 通常処理
if( signal == WAIT_OBJECT_0 )
{
...
}
// 終了処理
else if( signal == WAIT_OBJECT_0 + 1 )
{
while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
Write_stdout( "end / playing_thread" );

loop = 0;
}
}
}
}

Write_stdout( "playing_thread / thread end" );
return 0;
}
...

...
THREAD_MANAGER thread;
...
{
// イベント生成用の文字列
TCHAR str[] = "PlayingThread";

thread.event = CreateEvent( NULL, FALSE, FALSE, str );
thread.thread = CreateThread( NULL, 0, ThreadFunction,
(void*)(&thread),
0, &thread.threadID );
timeSetEvent( delay, span,
(LPTIMECALLBACK)(thread.event), 0,
TIME_PERIODIC | TIME_CALLBACK_EVENT_PULSE );
}
...
終了処理等は省略。

 何故か、構造体のメンバのポインタをそのまんま渡してやると

正常に動作します(謎)

 いろいろやってみたのですが、イベント単体で渡してしまうとどうにもこうにも上手くいかないようです…。

…こちらの方がスレッド関連の情報が一つにまとめられているので使いやすいといえば使いやすいですが…。

(確か「HANDLE」は特殊なvoid*か何かだったと思うので、それが関係あるのかな…。)





*1:途中のシグナル待ち・実行間隔調整で止まるものとする