EternalWindows
名前付きパイプ / パイプとオーバーラップ

GUIアプリケーションで名前付きパイプを使用する場合は、 ブロッキングの問題を慎重に扱わなければなりません。 サーバーはクライアントからの接続に応答するためにConnectNamedPipeを呼び出しますが、 この呼び出しでメインスレッドが待機してしまっては、 ウインドウを操作できなくなる問題があります。 こうした問題の解決策の常は、ワーカースレッドを作成することであり、 単純に考えれば、前節のコードをそのスレッドで実行すればよいだけのように思えます。

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	HANDLE hPipe;
	TCHAR  szData[256];
	DWORD  dwResult;

	hPipe = CreateNamedPipe(TEXT("\\\\.\\pipe\\SamplePipe"), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE, 1, sizeof(szData), sizeof(szData), 1000, NULL);
	if (hPipe == INVALID_HANDLE_VALUE) {
		MessageBox(NULL, TEXT("パイプの作成に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	ConnectNamedPipe(hPipe, NULL);
	
	ReadFile(hPipe, szData, sizeof(szData), &dwResult, NULL);
	WriteFile(hPipe, szData, (lstrlenW(szData) * sizeof(TCHAR)) + 1, &dwResult, NULL);

	CloseHandle(hPipe);
}

上記コードの場合、ConnectNamedPipeで待機するのはワーカースレッドであり、 ウインドウの操作に何らかの影響が出ることはありません。 しかし、このスレッドはウインドウが閉じられるタイミングを検出していないという問題があります。 ウインドウが閉じられてアプリケーションが終了すれば、ワーカースレッドは強制終了することになり、 クリーンアップ処理などを行う機会を失ってしまいます。 この解決策の常は、ウインドウが閉じられる前にイベントオブジェクトをシグナル状態にし、 ワーカースレッドでそれを検出するというものですが、今回の場合はそう簡単にはいきません。 なぜなら、そのイベントオブジェクトがシグナル状態になるまで待機してしまっては、 ConnectNamedPipeを呼び出して待機することができなくなるからです。 それではどうするかというと、ここでオーバーラップ(非同期)という仕組みを使用します。

まず、今回のサーバーのコードを示します。

#include <windows.h>

HANDLE g_hEventExit;

BOOL WaitEvent(HANDLE hEvent);
DWORD WINAPI ThreadProc(LPVOID lpParameter);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample-server");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HANDLE hThread = NULL;

	switch (uMsg) {

	case WM_CREATE:
		g_hEventExit = CreateEvent(NULL, TRUE, FALSE, NULL);
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);
		return 0;

	case WM_DESTROY:
		if (hThread != NULL) {
			SetEvent(g_hEventExit);
			WaitForSingleObject(hThread, 1000);
			CloseHandle(hThread);
		}

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	HANDLE     hPipe;
	HANDLE     hEvent;
	OVERLAPPED ov;
	TCHAR      szData[256];
	DWORD      dwResult;

	hPipe = CreateNamedPipe(TEXT("\\\\.\\pipe\\SamplePipe"), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, sizeof(szData), sizeof(szData), 1000, NULL);
	if (hPipe == INVALID_HANDLE_VALUE) {
		MessageBox(NULL, TEXT("パイプの作成に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	for (;;) {
		ZeroMemory(&ov, sizeof(OVERLAPPED));
		ov.hEvent = hEvent;
		ConnectNamedPipe(hPipe, &ov);
		if (!WaitEvent(hEvent))
			break;
		
		ReadFile(hPipe, szData, sizeof(szData), &dwResult, NULL);
		WriteFile(hPipe, szData, (lstrlenW(szData) * sizeof(TCHAR)) + 1, &dwResult, NULL);

		DisconnectNamedPipe(hPipe);
	}

	CloseHandle(hEvent);
	CloseHandle(hPipe);

	return 0;
}

BOOL WaitEvent(HANDLE hEvent)
{
	HANDLE hEventArray[2];
	DWORD  dwEventNo;

	hEventArray[0] = g_hEventExit;
	hEventArray[1] = hEvent;
	dwEventNo = WaitForMultipleObjects(2, hEventArray, FALSE, INFINITE) - WAIT_OBJECT_0;
	if (dwEventNo == 0)
		return FALSE;
	else if (dwEventNo == 1)
		return TRUE;
	else
		return FALSE;
}

ConnectNamedPipeの第2引数にはOVERLAPPED構造体を指定できます。 この場合、CreateNamedPipeの第2引数にはFILE_FLAG_OVERLAPPEDを指定し、 OVERLAPPED.hEventにはイベントオブジェクトを指定します。 この状態でConnectNamedPipeを呼び出すと、関数は直ちに制御を返すことになり、 hEventがシグナル状態になることで接続の完了を検出できます。 このための待機は、WaitEventで行われています。

BOOL WaitEvent(HANDLE hEvent)
{
	HANDLE hEventArray[2];
	DWORD  dwEventNo;

	hEventArray[0] = g_hEventExit;
	hEventArray[1] = hEvent;
	dwEventNo = WaitForMultipleObjects(2, hEventArray, FALSE, INFINITE) - WAIT_OBJECT_0;
	if (dwEventNo == 0)
		return FALSE;
	else if (dwEventNo == 1)
		return TRUE;
	else
		return FALSE;
}

既に述べたように、hEventに関する待機だけ行ってはウインドウが閉じられるタイミングを検出できませんから、 複数のイベントオブジェクトを指定できるWaitForMultipleObjectsを呼び出します。 g_hEventExitはウインドウが閉じられる際にシグナル状態になるため、これを検出するために配列へ指定します。 もちろん、クライアントからの接続も検出しなければなりませんから、hEventも配列へ指定します。 dwEventNoが0であるということはhEventArray[0]がシグナル状態になったことを意味し、 ウインドウが閉じられようとしていると判断できます。 この場合は、クライアントからの接続に応答するためのループを実行し続ける必要はありませんから、FALSEを返すようにしています。 一方、dwEventNoが1の場合はhEventArray[1]がシグナル状態になったことを意味し、 ループを続行するためにTRUEを返します。

WaitEventが成功したらクライアントが接続してきたということなので、 ReadFileでデータを読み取り、WriteFileでデータを書き込みます。 これが終了したらDisconnectNamedPipeで接続を切断し、 再びConnectNamedPipeを呼び出してクライアントからの接続を待ちます。 同じパイプを複数回ConnectNamedPipeに指定する場合は、 以前の接続をDisconnectNamedPipeで切断しておかなければならないことに注意してください。

続いて、クライアントのコードを示します。

#include <windows.h>

#define ID_EXCHANGE 100

DWORD WINAPI ThreadProc(LPVOID lpParamater);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample-client");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND   hwndButton = NULL;
	static HANDLE hThread = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hwndButton = CreateWindowEx(0, TEXT("BUTTON"), TEXT("データ交換"), WS_CHILD | WS_VISIBLE, 30, 50, 120, 30, hwnd, (HMENU)ID_EXCHANGE, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		return 0;

	case WM_COMMAND:
		EnableWindow(hwndButton, FALSE);
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, hwndButton, NULL, NULL);
		return 0;

	case WM_DESTROY:
		if (hThread != NULL) {
			WaitForSingleObject(hThread, 1000);
			CloseHandle(hThread);
		}

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	HANDLE hPipe;
	TCHAR  szBuf[256];
	TCHAR  szData[] = TEXT("sample-data");
	DWORD  dwResult;
	HWND   hwndButton = (HWND)lpParamater;

	hPipe = CreateFile(TEXT("\\\\.\\pipe\\SamplePipe"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (hPipe == INVALID_HANDLE_VALUE) {
		MessageBox(NULL, TEXT("パイプへの接続に失敗しました。"), NULL, MB_ICONWARNING);
		EnableWindow(hwndButton, TRUE);
		return 0;
	}

	WriteFile(hPipe, szData, sizeof(szData), &dwResult, NULL);
	ReadFile(hPipe, szBuf, sizeof(szBuf), &dwResult, NULL);

	MessageBox(NULL, szBuf, TEXT("クライアント"), MB_OK);

	CloseHandle(hPipe);
	EnableWindow(hwndButton, TRUE);

	return 0;
}

データ交換というボタンが押された場合は、スレッドを作成するためにCreateThreadを呼び出します。 これは、メインスレッドで接続や受信を行うと、その間にGUI操作ができなくなるからです。 ThreadProcの内容は前節と同じ要領です。


戻る