今回からはパイプについて少しやります。パイプとはプロセスとプロセスとをつなぐ まさにパイプのようなものです。パイプを通してプロセス間で通信を行うことができます。 これには、匿名パイプと名前付パイプがあります。パイプを作るプロセスをサーバーと呼び、 他のプロセスをクライアントと呼ぶことがあります。匿名パイプではサーバーからクライアント、 またはクライアントからサーバーのどちらか一方向だけの通信となります。 また、ネットワーク上の他のコンピュータのプロセスとは通信できません。
匿名パイプの作り方を次に示します。
親はパイプに書き込むだけ、子はパイプから読み出すだけの場合1.CreatePipe 関数で匿名パイプを作る(read, writeハンドルを受け取る) 2.read, writeハンドルのどちらか一方をクライアントに送る(通常「継承」を使う)
というような感じになります。わかりにくいのがDupicateHandle関数の部分だと思います。 普通はこの関数は他のプロセスでもハンドルが使えるようにするために用います。 この場合、コピー元も、コピー先も自分のプロセスになっています。 こうすることによってhWriteの継承属性を変更しているわけです。SECURITY_ATTRIBUTES sa; STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE hRead, hWrite; ... sa.bInheritHandle = TRUE; ... CreatePipe(&hRead, &hWrite, &sa, 0); //このあとすぐにCreateProcess関数を呼ぶと //read, writeハンドルの両方が継承されてしまう //子供は読むだけなのでhWriteが継承されてはまずい //hWriteを継承できないようにするため //DuplicateHandle関数を使う ... DuplicateHandle( GetCurrentProcess(),//ソースプロセス hWrite,//オリジナルハンドル GetCurrentProcess(),//行き先プロセス NULL,//複製ハンドルのポインタ NULLなので新規には作らない 0,//アクセス権 FALSE,//継承しない DUPLICATE_SAME_ACCESS);//5番目の引数を無視する ... si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = hRead; si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); si.hStdError = GetStdHandle(STD_ERROR_HANDLE); ... CreateProcess(image, command line, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); ... hProcess = pi.hProcess; CloseHandle(pi.hThread); CloseHandle(hRead);
匿名パイプを作ります。BOOL CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES hWritePipe, DWORD nSize );
hReadPipeにはreadハンドルのポインタを指定します。
hWritePipeにはwriteハンドルのポインタを指定します。
hWritePipeにはSECURITY_ATTRIBUTES構造体へのポインタを指定します。
これによりハンドルを継承するかどうかを決めます。
nSizeにはパイプのバッファサイズを指定します。0にするとデフォルトの値が使われます。
先に説明したとおりです。BOOL DuplicateHandle( HANDLE hSourceProcessHandle, // ソースプロセス HANDLE hSourceHandle, // 複製するオリジナルハンドル HANDLE hTargetProcessHandle, // コピー先プロセス LPHANDLE lpTargetHandle, // 複製ハンドルのポインタ DWORD dwDesiredAccess, // アクセス権 BOOL bInheritHandle, // 継承するかどうか DWORD dwOptions // オプション );
標準入出力、標準エラーデバイスのハンドルを取得します。HANDLE GetStdHandle( DWORD nStdHandle );
nStdHandleには、STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE のいずれかを指定します。
失敗したときはINVALID_HANDLE_VALUEが返されます。
たとえば、
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
WriteFile(hOutput, szStr, strlen(szStr), &dwWritten, NULL);
とすればコンソールにszStrの内容が表示されます。
では、プログラムを見てみましょう。今回は親プロセスを作ります。 メニューの「プロセス」「子プロセスを起動」で子供プロセスと匿名ハンドルを 作ります。子供プロセスはまだ作っていないので、実際にはエラーとなります。 (次章で子供プロセスを作ります。)
メニューの「プロセス」「パイプに書き込み」でダイアログボックスが 出現します。このダイアログボックスのエディットコントロールに 文字列を書いて、「OK」ボタンを押すとその文字列がパイプに書き込まれます。
普通のメニューと、ダイアログボックスのリソース・スクリプトです。// process02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "プロセス(&P)" BEGIN MENUITEM "子プロセスを起動(&C)", IDM_PRO MENUITEM "パイプに書き込む(&W)", IDM_WRITE END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 187, 81 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "書き込み" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,7,173,52,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN DEFPUSHBUTTON "OK",IDOK,7,60,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,60,50,14 END
hWriteをグローバル変数にしておきました。// process02.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MyChildProcess(HWND); HANDLE hWrite, hProcess; char szBuf[256]; HINSTANCE hInst; char szClassName[] = "process02"; //ウィンドウクラス
いつもと同じです。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座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
WM_CREATEメッセージが来たらメニューハンドルを取得しておきます。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HMENU hMenu; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); break; case WM_INITMENU: if (hWrite) { EnableMenuItem(hMenu, IDM_PRO, MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hMenu, IDM_WRITE, MF_ENABLED | MF_BYCOMMAND); } else { EnableMenuItem(hMenu, IDM_PRO, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hMenu, IDM_WRITE, MF_GRAYED | MF_BYCOMMAND); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_WRITE: DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)DlgProc); break; case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_PRO: MyChildProcess(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { if (hProcess) CloseHandle(hProcess); if (hWrite) CloseHandle(hWrite); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
WM_INITMENUメッセージが来たらhWriteによって場合わけをしています。 これが有効なら「子プロセスを起動」を使用可能に、「パイプに書き込む」を 使用不可能にします。hWriteがNULLならこの反対です。
メニューでIDM_WRITEが選択されたらダイアログボックスを出します。
IDM_PROが選択されたらMyChildProcess関数を呼んで匿名パイプと子プロセスを作ります。
このプログラムの終了時にhProcessとhWriteをCloseHandleで閉じます。
この部分の解説はすでに終わっています。void MyChildProcess(HWND hWnd) { HANDLE hRead; SECURITY_ATTRIBUTES sa; STARTUPINFO si; PROCESS_INFORMATION pi; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { MessageBox(hWnd, "パイプの作成に失敗しました", "Error", MB_OK); return; } if (!DuplicateHandle(GetCurrentProcess(), //ソースプロセス hWrite, //duplicateするハンドル(オリジナルハンドル) GetCurrentProcess(), //ターゲットプロセス(行先) NULL, //複製ハンドルへのポインタ(コピーハンドル) 0, //アクセス権 FALSE, //子供がハンドルを継承するかどうか DUPLICATE_SAME_ACCESS)) { //オプション MessageBox(hWnd, "DuplicateHandle Error", "Error", MB_OK); CloseHandle(hWrite); CloseHandle(hRead); hWrite = NULL; return; } memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = hRead; si.hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); si.hStdError = GetStdHandle( STD_ERROR_HANDLE ); if (!CreateProcess(NULL, "process02x.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { MessageBox(hWnd, "CreateProcess Error", "Error", MB_OK); CloseHandle(hWrite); hWrite = NULL; return; } hProcess = pi.hProcess; CloseHandle(pi.hThread); CloseHandle(hRead); return; }
ダイアログボックスのプロシージャです。LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { DWORD dwResult, dwError; switch (msg) { case WM_INITDIALOG: SetFocus(GetDlgItem(hDlg, IDC_EDIT1)); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(GetDlgItem(hDlg, IDC_EDIT1), szBuf, sizeof(szBuf)); if (!WriteFile(hWrite, szBuf, sizeof(szBuf), &dwResult, NULL)) { dwError = GetLastError(); if (dwError == ERROR_BROKEN_PIPE || dwError == ERROR_NO_DATA) { MessageBox(hDlg, "パイプがありません", "Error", MB_OK); } else { MessageBox(hDlg, "何らかのエラーです", "Error", MB_OK); } CloseHandle(hWrite); hWrite = NULL; CloseHandle(hProcess); hProcess = NULL; } EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }
「OK」ボタンが押されるとエディットコントロールの文字列が読み出されて WriteFile関数でパイプに書き込まれます。パイプに書き込むにはWriteFile関数を 使います。書き込みが失敗したときはGetLastError関数でエラーの原因を調査します。 エラーが起こった場合hWriteやhProcessをCloseHandleで閉じて、NULLにしておきます。
さて、今回は匿名パイプのサーバー側を作りました。クライアント側はそれほど難しくないので 作ってみてください。このとき名前をprocess02x.exeにしておき、このプログラムのexeのある ディレクトリに置いておきます。
Update 29/Nov/1999 By Y.Kumei