第248章 双方向から読み書きできる名前付パイプ


今回は、サーバー・クライアントの双方向から書き込みのできる 名前付きパイプを作ります。名前付きパイプはWin95/98では使えないので 注意してください。



使い方は、まずサーバー側のプログラムを立ち上げて、メニューの 「パイプ」「パイプの作成」を実行します。これを実行しておかないと クライアント側のプログラムが立ち上がらないので注意してください。 その後、クライアント側のプログラムを実行します。サーバー、クライアント どちらからもパイプに書き込みができます。パイプに書き込まれると 反体側のプログラムから書き込まれた内容を表示するメッセージボックスが 出現します。

クライアント側からの書き込みで、サーバー側がこれを読みとるのは 前章のプログラムと同じ仕組みです。サーバー側からの書き込みに対して クライアント側で読みとる場合もほぼ同様の仕組みでよいのですがいくつか 注意点があります。その一つはイベントオブジェクトを2つ用意しておかないと どちらの書き込みか判断が付かず、一方のスレッドが不用意に終了して しまう、という点です。
では、最初にサーバー側プログラムからみてみましょう。

// namedp02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)...", IDM_END END POPUP "パイプ(&P)" BEGIN MENUITEM "パイプの作成(&C)", IDM_PIPE MENUITEM "パイプに書き込み(&W)...", IDM_WRITE END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYWRITE DIALOG DISCARDABLE 0, 0, 187, 93 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "パイプへの書き込み(サーバー)" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,7,173,51,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN DEFPUSHBUTTON "OK",IDOK,7,72,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,72,50,14 END

前章に比べてメニューの「パイプに書き込み(&W)...」 というのが増えました。また、書き込む内容を入力するためのダイアログボックス も増えました。

// namedp02.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include "resource.h" typedef struct _tagmydata { BOOL bEnd; HANDLE hPipe; } MYDATA; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyWriteProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HANDLE MakeMyPipe(HWND, MYDATA *); DWORD WINAPI MyThreadProc(LPVOID); char szClassName[] = "namedp02"; //ウィンドウクラス HANDLE hEvent, hThread; HINSTANCE hInst;

ダイアログボックス関係のマクロを使う関係でwindowsx.hを インクルードしています。また、MakeMyPipe関数の戻り値をint 型からHANDLE型に変更してパイプハンドルを返すようにしました。
また、インスタンスハンドルをグローバル変数にコピーしておくようにしました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかる名前付パイプ", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 200, //幅 150, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

いつもとほとんど同じですが、親ウィンドウの初期サイズを小さめにしておきました。 WinMain関数の最初の方でインスタンスハンドルをグローバル変数にコピーしている点に 注意してください。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static MYDATA md; static HANDLE hPipe; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_PIPE: hPipe = MakeMyPipe(hWnd, &md); break; case IDM_WRITE: if (!hPipe) { MessageBox(hWnd, "パイプがありません", "サーバー", MB_OK); break; } DialogBoxParam(hInst, "MYWRITE", hWnd, (DLGPROC)MyWriteProc, (LPARAM)hPipe); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { md.bEnd = TRUE; SetEvent(hEvent); if (CloseHandle(hThread)) MessageBox(hWnd, "hThreadをクローズしました", "サーバー", MB_OK); Sleep(2000); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

メニューのIDM_PIPEが来たら自作関数MakeMyPipeで名前付きパイプを作り パイプハンドルをstatic変数に格納しておきます。

メニューのIDM_WRITEが選択されたら、書き込み用ダイアログボックスを出します。 このときDialogBoxParam関数を使ってパイプハンドルをダイアログボックスの プロシージャに教えてやります。 DialogBoxParam関数については第195章を参照してください。

HANDLE MakeMyPipe(HWND hWnd, MYDATA *lpmd) { DWORD dwThreadID; static HANDLE hPipe; hPipe = CreateNamedPipe("\\\\.\\pipe\\mypipe", PIPE_ACCESS_DUPLEX, //オープンモード PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, //パイプモード 1, //パイプに対する最大インスタンス 1024,//出力バッファ 1024,//入力バッファ 0,//タイムアウト NULL); if (hPipe == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "パイプ作成失敗", "サーバー", MB_OK); return NULL; } hEvent = CreateEvent(NULL, //セキュリティ属性 FALSE, //手動リセット FALSE, //初期状態 "yasutaka");//名前 lpmd->bEnd = FALSE; lpmd->hPipe = hPipe; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadProc, (LPVOID)lpmd, 0, &dwThreadID); if (hThread == NULL) { MessageBox(hWnd, "スレッドの作成に失敗しました", "サーバー", MB_OK); CloseHandle(hPipe); return NULL; } return hPipe; }

前章とほとんど同じですがCreateNamedPipe関数の オープンモードをPIPE_ACCESS_DUPLEXにしています。また、関数の戻り値が パイプハンドルになっています。パイプの作成が失敗したときはNULLを返します。

DWORD WINAPI MyThreadProc(LPVOID lpParam) { char szBuf[1024]; HANDLE hPipe; DWORD dwRead; BOOL bRead = FALSE; MYDATA *lpmd; lpmd = (MYDATA *)lpParam; hPipe = lpmd->hPipe; while (1) { WaitForSingleObject(hEvent, INFINITE); if (lpmd->bEnd == TRUE) { MessageBox(NULL, "スレッド終了フラグが来ました", "サーバー", MB_OK); break; } memset(szBuf, '\0', sizeof(szBuf)); bRead = ReadFile(hPipe, szBuf, sizeof(szBuf), &dwRead, NULL); ResetEvent(hEvent); if (bRead == FALSE) { MessageBox(NULL, "読み出し失敗", "サーバー", MB_OK); break; } MessageBox(NULL, szBuf, "サーバー", MB_OK); } MessageBox(NULL, "ループを抜けました", "サーバー", MB_OK); if (CloseHandle(hPipe)) MessageBox(NULL, "hPipeをクローズしました", "サーバー", MB_OK); return 0; }

クライアント側の書き込みがあれば内容をメッセージボックスで表示するスレッド関数です。 内容的には前章のものと全く同じですがメッセージボックスのウィンドウタイトルを 「サーバー」に変えました。と、いうのもいろいろメッセージボックスがでてくるので どちら側から出されたメッセージボックスかはっきりさせるためです。

LRESULT CALLBACK MyWriteProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HANDLE hPipe; static HWND hEdit; char szBuf[1024]; DWORD dwWritten; static HANDLE hEvent2; switch (msg) { case WM_INITDIALOG: hEdit = GetDlgItem(hDlg, IDC_EDIT1); hPipe = (HANDLE)lp; hEvent2 = OpenEvent(EVENT_MODIFY_STATE, FALSE, "yasutaka2"); if (hEvent2 == NULL) { MessageBox(hDlg, "OpenEvent Error", "サーバー", MB_OK); EndDialog(hDlg, NULL); } return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hEdit, szBuf, sizeof(szBuf)); SetEvent(hEvent2); WriteFile(hPipe, szBuf, strlen(szBuf), &dwWritten, NULL); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }

書き込み用ダイアログボックスのプロシージャです。

WM_INITDIALOGメッセージが来たらエディットコントロールのウィンドウハンドルを 調べてstatic変数に格納しておきます。また、パイプハンドルをlpから取得して static変数に保存しておきます。さらに、OpenEvent関数で"yasutaka2"のイベントオブジェクト のハンドルも調べておきます。まだクライアント側が起動されていないときは"yasutaka2"オブジェクト は存在しないのでエラーとなりダイアログボックスを閉じます。

OKボタンが押されたらエディットコントロールの内容を読み出して、これをパイプに書き込みます。

では、クライアント側のプログラムをみてみましょう。

// namedpx02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)...", IDM_END END POPUP "通信(&C)" BEGIN MENUITEM "パイプに書き込む(&P)...", IDM_PIPE END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 151, 93 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "パイプへの書き込み(クライアント)" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,7,134,52,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN DEFPUSHBUTTON "OK",IDOK,7,72,50,14 PUSHBUTTON "キャンセル",IDCANCEL,94,72,50,14 END

前章とほとんど同じです。

// namedpx02.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include "resource.h" typedef struct _tagmydata { BOOL bEnd; HANDLE hPipe; } MYDATA; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); DWORD WINAPI MyThreadProc(LPVOID); char szClassName[] = "namedpx02"; //ウィンドウクラス HINSTANCE hInst; HANDLE hEvent, hFile, hThread, hEvent2;

今回はクライアント側でもパイプへの書き込みを見張るため 読み出しようのスレッドを作ります。そのスレッドとの連絡用に MYDATA構造体を定義しておきます。

グローバル変数にhThread, hEvent2が増えました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "クライアント", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 200, //幅 150, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

毎度同じですが、親ウィンドウを少し小さめにしてみました。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; DWORD dwThreadID; static MYDATA md; switch (msg) { case WM_CREATE: hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, "yasutaka"); if (hEvent == NULL) { MessageBox(hWnd, "Error OpenEvent", "クライアント", MB_OK); DestroyWindow(hWnd); break; } hFile = CreateFile("\\\\.\\pipe\\mypipe", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "パイプをオープンできませんでした", "クライアント", MB_OK); DestroyWindow(hWnd); break; } hEvent2 = CreateEvent(NULL, FALSE, FALSE, "yasutaka2"); if (hEvent2 == NULL) { MessageBox(hWnd, "CreateEvent Error", "クライアント", MB_OK); CloseHandle(hFile); break; } md.bEnd = FALSE; md.hPipe = hFile; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThreadProc, (LPVOID)&md, 0, &dwThreadID); if (hThread == NULL) { MessageBox(hWnd, "スレッドの作成に失敗しました", "クライアント", MB_OK); CloseHandle(hFile); return NULL; } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_PIPE: DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { md.bEnd = TRUE; SetEvent(hEvent2); Sleep(2000); if (CloseHandle(hThread)) MessageBox(hWnd, "hThread をクローズしました", "クライアント", MB_OK); if (hFile) if (CloseHandle(hFile)) MessageBox(hWnd, "hFileをクローズしました", "クライアント", MB_OK); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_CREATEメッセージが来たら、"yasutaka"オブジェクトのハンドルを取得するところは同じです。 次にCreateFile関数の2番目の引数をGENERIC_WRITE | GENERIC_READにしておきます。

次にクライアントのスレッド用のイベントオブジェクト"yasutaka2"を作ります。 そして、CreateThread関数でスレッドを作ります。スレッドに渡すパイプハンドルは CreateFile関数で取得したhFileであることに注意してください。

このプログラムの終了時の後始末にも注意してください。

LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { DWORD dwWritten; char szBuf[1024]; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(GetDlgItem(hDlg, IDC_EDIT1), szBuf, sizeof(szBuf)); if (!SetEvent(hEvent)) MessageBox(hDlg, "SetEvent Error", "クライアント", MB_OK); WriteFile(hFile, szBuf, strlen(szBuf), &dwWritten, NULL); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }

ダイアログボックスのプロシージャです。内容的には前章のものと全く同じですが、 メッセージボックスのウィンドウタイトルを「クライアント」に変えています。

DWORD WINAPI MyThreadProc(LPVOID lpParam) { char szBuf[1024]; HANDLE hPipe; DWORD dwRead; BOOL bRead = FALSE; MYDATA *lpmd; lpmd = (MYDATA *)lpParam; hPipe = lpmd->hPipe; while (1) { WaitForSingleObject(hEvent2, INFINITE); if (lpmd->bEnd == TRUE) { MessageBox(NULL, "スレッド終了フラグが来ました", "クライアント", MB_OK); break; } memset(szBuf, '\0', sizeof(szBuf)); bRead = ReadFile(hPipe, szBuf, sizeof(szBuf), &dwRead, NULL); ResetEvent(hEvent2); if (bRead == FALSE) { MessageBox(NULL, "読み出し失敗", "クライアント", MB_OK); break; } MessageBox(NULL, szBuf, "クライアント", MB_OK); } MessageBox(NULL, "ループを抜けました", "クライアント", MB_OK); return 0; }

クライアント側のスレッド関数です。イベントオブジェクト"yasutaka2"がシグナル状態に なったらパイプからデータを読み出してメッセージボックスで内容を表示します。

MYDATA構造体のbEndメンバがTRUEになったら無限ループを抜けてスレッド関数を 終了させます。

今回のプログラムでは、サーバー側とクライアント側がそれぞれ1つずつしか対応していません。 サーバー側のプログラムを立ち上げて、その後2つのクライアントプログラムを 立ち上げると2つ目のクライアントは失敗するようにしてあります。

また、この2つのプログラムは同一マシンにないと失敗します。 いろいろ改良してみてください。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 05/Jan/2000 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。