空気なんて読みませんよ。
Windows APIで、トップレベルウィンドウを列挙するサンプルコード。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>
static BOOL CALLBACK EnumWindowProc( HWND hWnd, LPARAM lParam )
{
if( IsWindowVisible( hWnd ) )
{
TCHAR szTitle[ 256 ];
GetWindowText( hWnd, szTitle, 256 );
_tprintf( _T( "0x%08.8X, %s\n" ), hWnd, szTitle );
}
return TRUE;
}
static void EnumWindows1()
{
EnumWindows( &EnumWindowProc, 0 );
}
int main()
{
_tsetlocale( LC_ALL, _T( "japanese" ) );
EnumWindows1();
_putts( _T( "" ) );
_putts( _T( "何かキーを押すと終了します。" ) );
getchar();
return 0;
}
非表示でないトップレベルウィンドウを列挙して、ウィンドウハンドル値とタイトルを表示します。
これといって特筆すべきことはありません。
これを、「列挙関数を一度呼ぶごとにウィンドウハンドルを1つ返す」という形にしたいと思います。
まず、ヘッダファイル。ファイル名はEnumWnd.hとします。
#pragma once
typedef struct ENUMWND * HENUMWND;
HENUMWND BeginEnumWindows();
HWND EnumWindow( HENUMWND hEnumWnd );
void EndEnumWindows( HENUMWND hEnumWnd );
ハンドル型と、列挙の開始関数、ウィンドウを1つ列挙する関数、後始末関数を宣言します。
続いて、ソース本体。ファイル名はEnumWnd.cppとしましょう。
制御が移っていく順番を書いてみました。「数字→」から「→数字」へジャンプします。
実行される様子を追いたい場合は、SwitchToFiberの前後にブレークポイントを置いてステップ実行するとよいでしょう。
#include <windows.h>
#include "EnumWnd.h"
struct ENUMWND
{
LPVOID pMainFiber;
LPVOID pEnumeratorFiber;
HWND hwndCurrent;
};
static BOOL CALLBACK EnumWindowProc( HWND hWnd, LPARAM lParam )
{
// →2
HENUMWND hEnumWnd = ( HENUMWND )lParam;
hEnumWnd->hwndCurrent = hWnd;
SwitchToFiber( hEnumWnd->pMainFiber ); // →3
// →4
return TRUE; // 列挙中は2→、最後のウィンドウだったら5→
}
static void CALLBACK FiberProc( LPVOID pvParam )
{
// →1
HENUMWND hEnumWnd = ( HENUMWND )pvParam;
EnumWindows( &EnumWindowProc, ( LPARAM )hEnumWnd ); // 2→
// →5
for( ; ; )
{
hEnumWnd->hwndCurrent = NULL;
SwitchToFiber( hEnumWnd->pMainFiber ); // 3→
}
}
HENUMWND BeginEnumWindows()
{
HENUMWND hEnumWnd = new ENUMWND;
hEnumWnd->pMainFiber = ConvertThreadToFiber( NULL );
hEnumWnd->pEnumeratorFiber = CreateFiber( 0, &FiberProc, hEnumWnd );
hEnumWnd->hwndCurrent = NULL;
return hEnumWnd;
}
HWND EnumWindow( HENUMWND hEnumWnd )
{
SwitchToFiber( hEnumWnd->pEnumeratorFiber ); // 初回は1→、2回目以降は4→
// →3
return hEnumWnd->hwndCurrent;
}
void EndEnumWindows( HENUMWND hEnumWnd )
{
DeleteFiber( hEnumWnd->pEnumeratorFiber );
ConvertFiberToThread();
delete hEnumWnd;
}
ついでに、呼び出し側のコード。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>
#include "EnumWnd.h"
static void EnumWindows2()
{
HENUMWND hEnumWnd = BeginEnumWindows();
HWND hWnd = EnumWindow( hEnumWnd );
while( hWnd != NULL )
{
if( IsWindowVisible( hWnd ) )
{
TCHAR szTitle[ 256 ];
GetWindowText( hWnd, szTitle, 256 );
_tprintf( _T( "0x%08.8X, %s\n" ), hWnd, szTitle );
}
hWnd = EnumWindow( hEnumWnd );
}
EndEnumWindows( hEnumWnd );
}
int main()
{
_tsetlocale( LC_ALL, _T( "japanese" ) );
EnumWindows2();
_putts( _T( "" ) );
_putts( _T( "何かキーを押すと終了します。" ) );
getchar();
return 0;
}
BeginEnumWindowsは、データを収める構造体を確保して、中身を初期化します。
まず、呼び出し側スレッドをファイバ化します(ConvertThreadToFiber)。
次に、新しいファイバを作ります(CreateFiber)。
ファイバはスレッドと違い、作っただけでは実行を開始しません。FiberProcはまだ眠りについたまま、制御は呼び出し側(EnumWindows2)へ戻ります。
EnumWindows2からEnumWindowが呼ばれます。
この中ではSwitchToFiberが呼ばれています。引数はBeginEnumWindowsで作った、FiberProcを指し示すファイバポインタなので、ここで初めて、FiberProc(1)が実行され始めます。
と同時に、EnumWindowの実行は一旦中断されます。
FiberProcの中ではEnumWindows(EnumWindowではありません)が呼ばれ、制御はEnumWindowsProc(2)に移ります。
ここは何の変哲もありません。
EnumWindowsProcの中で、1つウィンドウハンドルが取得できたので、呼び出し側に制御を返します。
BeginEnumWindows内でConvertThreadToFiberで作成したファイバを指定してSwitchToFiberを呼ぶことで、EnumWindowの中断していた箇所(3)に制御が戻り、同時に、EnumWindowProcの実行が一時中断されます。
EnumWindowから、呼び出し元であるEnumWindows2に戻り、非表示でなければ、ウィンドウの情報が表示されます。
再度EnumWindowが呼ばれ、SwitchToFiberが呼ばれると、EnumWindowProcの中断していた箇所(4)に制御が移ります。
ウィンドウの列挙が全て完了するまで、以上の繰り返しになります。
全てのウィンドウを列挙し終えると、FiberProc(5)に戻ってきます。
その後は、無限にループしているように見えますが、そうではありません。
ループ中のSwitchToFiberでEnumWindow(3)に戻り、呼び出し元のEnumWindows2では結果がNULLなのでループを中断します。
EndEnumWindowsで後始末をして、プログラムが終了します。
FiberProcの中で無限ループのようなコードを書いていた理由は、29行目をコメントアウトし(forブロック全体をコメントアウトしないように)、呼び出し元のEnumWindows2で、EnumWindowがNULLを返した後、EndEnumWindowsを呼ぶ前に、もう一度だけEnumWindowを呼んでみればわかります。
static void CALLBACK FiberProc( LPVOID pvParam )
{
// →1
HENUMWND hEnumWnd = ( HENUMWND )pvParam;
EnumWindows( &EnumWindowProc, ( LPARAM )hEnumWnd ); // 2→
// →5
// for( ; ; )
{
hEnumWnd->hwndCurrent = NULL;
SwitchToFiber( hEnumWnd->pMainFiber ); // 3→
}
}
void EnumWindows2()
{
HENUMWND hEnumWnd = BeginEnumWindows();
HWND hWnd = EnumWindow( hEnumWnd );
while( hWnd != NULL )
{
if( IsWindowVisible( hWnd ) )
{
TCHAR szTitle[ 256 ];
GetWindowText( hWnd, szTitle, 256 );
_tprintf( _T( "0x%08.8X, %s\n" ), hWnd, szTitle );
}
hWnd = EnumWindow( hEnumWnd );
}
// もう一度呼んでみる
hWnd = EnumWindow( hEnumWnd );
EndEnumWindows( hEnumWnd );
}
mainで、何かキーを押すまで終了を待機しているはずなのに、19行目のEnumWindowの呼び出しから戻ることなく、プログラムが終了してしまいます。
このプログラムには、ファイバは2つありますが、スレッドは1つしかありません。
スレッドが終了するには、ExitThreadを呼ぶか、スレッドプロシージャからreturnしますね。
さて、このプログラムのスレッドプロシージャはドコでしょうか?
FiberProcには、呼び出し元がありません。
EnumWindow内のSwitchToFiberから呼び出されているようにも見えますが、それは1回目だけです。
2回目以降は、いきなりFiberProc(から呼ばれているEnumWindowProc)の途中から始まります。
呼び出し元がないのですから、帰る先もありません。
おわかりでしょうか。
このプログラムには、mainの最後とFiberProcの最後、スレッドプロシージャの終了地点が2つあるのです。
どちらかからreturnすれば、スレッドは終了してしまいます。
だから、FiberProcからは決してreturnしないように、無限ループのようなfor文を書いているのです。
FiberProc自体には戻り値がないのも、returnすることがないからでしょう。
最後に余談。
このプログラムにはスレッドが1つしかありませんが、マルチスレッドとファイバの組み合わせも面白いものがあります。
CreateFiberで作ったファイバは、CreateFiberの呼び出し元のスレッドに関連付けられません。
CreateFiberを呼んだスレッドと、そのファイバに制御を移すためのSwitchToFiberを呼ぶスレッドは異なっていてもかまいません。
ファイバは、SwitchToFiberの呼び出し側のスレッドで実行されます。
そのため、1つのファイバを実行するスレッドを複数用意して切り替えることができます。
ただし、複数のスレッドから同時に1つのファイバにアクセスすることはできません。