名前付きパイプによるプロセス間通信をやってみる
Windowsにおいてプロセス間通信(IPC)に使われる名前付きパイプについて調べました。
名前付きパイプ(Named Pipe)とは
名前付きパイプとは、プロセス間のデータ転送のためのプログラミングAPI。メールスロットと異なり、信頼される双方向通信を実現できる。ファイルのようにアクセスでき、Windows I/Oの標準関数であるCreateFile関数、ReadFile関数、WriteFile関数、CloseHandle関数を使って扱うことができる。生成時に作成するインスタンスの個数を指定できるため、1つのサーバが複数のクライアントと通信することもできる。また、サーバが自身のアカウント権限ではなくクライアント側のアカウント権限でスレッドの実行する、偽装(Impersonation)と呼ばれる機能も利用できる。名前付きパイプの関数は、kernel32.dllに実装されている。
CreateNamedPipe関数
名前付きパイプは、CreateNamedPipe関数を使ってインスタンスを作成できる。この関数の第一引数lpNameは、名前付きパイプ名へのポインタで、次の形式で指定する。
\\<サーバ>\Pipe\<パイプ名>
- <サーバ>の部分には、名前付きパイプサーバを実行するコンピュータを指定する。Windows定義のエイリアス「\\.\」を指定した場合は、そのシステムのみで有効となる。 本記事では未検証だが、名前付きパイプはネットワークにまたがるプロセス間通信にも利用可能で、<サーバ>の部分にDNS名、IPアドレス、NetBIOS名なども指定できる。
- <パイプ名>の部分には一意な名前を指定する。サブディレクトリを含むことができる。
例:\\.\Pipe\MyServerApp\ConnectionPipe
第二引数dwOpenModeでパイプの通信方向を指定できる。
値 | 説明 |
---|---|
PIPE_ACCESS_DUPLEX | 双方向。サーバとクライアントの両方が名前付きパイプの読み書きを行える。 |
PIPE_ACCESS_INBOUND | 一方向。データの流れをクライアントからサーバへの方向に限定する。 |
PIPE_ACCESS_OUTBOUND | 一方向。データの流れをサーバからクライアントへの方向に限定する。 |
パイプに関連する他の関数
関数名 | 説明 |
---|---|
ConnectNamedPipe | クライアントが名前付きパイプに接続してくるのを待機する。 |
CallNamedPipe | 名前付きパイプがメッセージタイプの場合は当該関数で接続する。 |
PeekNamedPipe | 名前付きパイプの状態を調べる。 |
WaitNamedPipe | 接続可能になるかタイムアウト時間が経過まで待機する。 |
詳細は、下記Webページにまとめられている。
https://msdn.microsoft.com/ja-jp/library/cc429276.aspx
名前付きパイプのアクティビティの確認方法
Windows Sysinternals の PipeListツールを使う。
docs.microsoft.com
名前付きパイプの名前、それに対して作成されているインスタンス数、最大インスタンス数を表示できる。
C:\Users\Ayum>C:\Users\Ayum\Downloads\PipeList\pipelist.exe PipeList v1.02 - Lists open named pipes Copyright (C) 2005-2016 Mark Russinovich Sysinternals - www.sysinternals.com Pipe Name Instances Max Instances --------- --------- ------------- InitShutdown 3 -1 lsass 4 -1 protected_storage 3 -1 ntsvcs 3 -1 scerpc 3 -1 plugplay 3 -1 Winsock2\CatalogChangeListener-484-0 1 1 epmapper 3 -1 (以下略)
プログラム
クライアント側で文字列を入力すると名前付きパイプを利用してプロセス間通信(一方向)を行い、サーバ側で文字列が表示されるというプログラム。実行する際は、名前付きパイプを作成してから読み込む必要があるのでサーバ側から先に実行する。終了する場合は、”EXIT”という文字列を入力する。
サーバ側のソースコード
サーバ側では、CreateNamedPipe関数を呼び出して名前付きパイプのインスタンスを作成し、ConnectNamedPipe関数でクライアントが接続するまで待機する。その後、名前付きパイプにクライアントからのメッセージがある場合は表示する。
#include <stdio.h> #include <windows.h> int main() { HANDLE hPipe = INVALID_HANDLE_VALUE; hPipe = CreateNamedPipe("\\\\.\\pipe\\mypipe", //lpName PIPE_ACCESS_INBOUND, // dwOpenMode PIPE_TYPE_BYTE | PIPE_WAIT, // dwPipeMode 1, // nMaxInstances 0, // nOutBufferSize 0, // nInBufferSize 100, // nDefaultTimeOut NULL); // lpSecurityAttributes if (hPipe == INVALID_HANDLE_VALUE) { fprintf(stderr, "Couldn't create NamedPipe."); return 1; } if (!ConnectNamedPipe(hPipe, NULL)) { fprintf(stderr, "Couldn't connect to NamedPipe."); CloseHandle(hPipe); return 1; } while (1) { char szBuff[256]; DWORD dwBytesRead; if (!ReadFile(hPipe, szBuff, sizeof(szBuff), &dwBytesRead, NULL)) { fprintf(stderr, "Couldn't read NamedPipe."); break; } if (!strncmp("EXIT", szBuff, 4)) { // Exit Check break; } szBuff[dwBytesRead] = '\0'; printf("Server : %s", szBuff); Sleep(1); } FlushFileBuffers(hPipe); DisconnectNamedPipe(hPipe); CloseHandle(hPipe); return 0; }
クライアント側のソースコード
クライアント側では、サーバ側で作成した名前付きパイプのハンドルを取得し、ユーザによって入力された文字列をWriteFile関数で書き込む。
#include <stdio.h> #include <windows.h> int main() { HANDLE hPipe = INVALID_HANDLE_VALUE; hPipe = CreateFile("\\\\.\\pipe\\mypipe", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPipe == INVALID_HANDLE_VALUE) { fprintf(stderr, "Couldn't create NamedPipe."); return 1; } while (1) { char szBuff[255]; DWORD dwBytesWritten; fgets(szBuff, sizeof(szBuff), stdin); if (!WriteFile(hPipe, szBuff, strlen(szBuff), &dwBytesWritten, NULL)) { fprintf(stderr, "Couldn't write NamedPipe."); break; } if (!strncmp("EXIT", szBuff, 4)) { // Exit Check break; } } CloseHandle(hPipe); return 0; }
実行結果
クライアントに文字列を入力してEnterを押下すると、サーバにも同一の文字列が表示される。このときの名前付きパイプのアクティビティは次の通りとなった。「mypipe」が表示されていることがわかる。
C:\Users\Ayum>C:\Users\Ayum\Downloads\PipeList\pipelist.exe PipeList v1.02 - Lists open named pipes Copyright (C) 2005-2016 Mark Russinovich Sysinternals - www.sysinternals.com Pipe Name Instances Max Instances --------- --------- ------------- InitShutdown 3 -1 lsass 4 -1 protected_storage 3 -1 ntsvcs 3 -1 scerpc 3 -1 plugplay 3 -1 (中略) mypipe 1 1
Process Hackerなどのツールを使ってクライアントのハンドル一覧を見れば、パイプのハンドルを確認できる。
上記コードを32bit/64bitプログラムにそれぞれコンパイルし、各プロセス間で名前付きパイプによる通信が可能か検証した。いずれも成功した。
サーバ | クライアント | プロセス間通信 |
---|---|---|
32bit | 32bit | 成功 |
32bit | 64bit | 成功 |
64bit | 32bit | 成功 |
64bit | 64bit | 成功 |