ホームに戻る
 17、プロセス間通信

プロセス間で通信を行うには何通りかのやり方があります。

まず通信をファイルを通して行う方法があります。
いったんハードディスクを介するので遅いのですが、
同時アクセスの問題を解決すれば、その他に難しいことはありません。

次にファイルマッピングという方法があります。
メモリ共有によるデータ交換はファイルマッピングの応用になるので
まずはファイルマッピングについて書きます。

ファイルマッピングはファイルの読み書きをメモリで行う方法。
まず CreateFile でファイルを開きます。
CreateFileMapping でファイルのマッピング対象を指定します。
指定サイズがファイルより小さい場合は先頭からのサイズとなり、
ファイルより大きいときはファイルサイズのほうが拡張されます。
ファイルマッピングには名前をつけることができ、
名前によって他プロセスからもアクセスできるのがポイント。
MapViewOfFile ではマッピング対象から視野(ビュー)を指定します。
指定できるのはオフセットとサイズになります。
このビュー範囲に書き込むとページングに似た方式で、
ファイル範囲がメモリに予約して割り当てられ、
ファイルへの書きこみをメモリの書き込みのように行うことができます。
このときのやり方はページングと同じように
よく使う領域が実メモリにより割り当てられるような工夫がされます。
またこの管理はOSが行うので実際にファイルが実メモリに読み込まれているかどうか?
ファイルに書きこまれているのがいつか?などはわかりません。
もし強制的にファイルに書き出す必要があれば、
FlushViewOfFile を使うことで
変更のあった部分をOSが判別して強制書き込みします。
基本的に UnmapViewOfFile する際に書き込みされるので問題ないのですが、
間に通常のファイルアクセスがある場合に食い違いが生じます。
ビューの解除は UnmapViewOfFile で行い。
ファイルマッピングはハンドルを閉じて片付けます。
その後でファイルのハンドルを閉じる。

ファイルマッピングの利点は
例えば巨大なファイルに対して使う部分だけ実メモリに呼び出すような
ページングのような効果によってメモリの節約が行える点。
またビューが複数ありファイルの重複する部分を各ビューに割り振っても
重複する部分に対しては共通の実メモリが割り振られるので、
複数のアクセスに対してもメモリ領域の重複を気にしなくとも良い。

プロセス間のメモリ共有に応用する場合、
ファイル名とファイルマッピングの名前を共通にして、
複数のプロセスからアクセスすることで
共通の実メモリ割り当てを可能にする。
この領域はあたかもグローバル変数に割り当てるように利用できるが、
実際はミューテックス等で同期をとる必要がある。
またファイルは指定しなくともマッピングは可能である。
マッピング時のファイルハンドルに INVALID_HANDLE_VALUE を指定すると、
適当にファイル領域を確保しメモリ共有を実現できる。

ウィンドウメッセージによって通信を行う方法もある。
送信の場合、

hTgtWnd = FindWindow(NULL, TARGET_CLASS_NAME);

COPYDATASTRUCT cds;
cds.dwData = 0; // DWORD 任意の数値
cds.cbData = strlen(s) + 1; // DWORD データサイズ
cds.lpData = s;
SendMessage(hTgtWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);

受信の場合、

COPYDATASTRUCT *pCDS;

switch(msg){
  case WM_COPYDTA:
    pCDS = (COPYDATASTRUCT *)lParam;

簡単ではあるが注意点はたくさんある。
まずアドレスは送信してはならない。
プロセスが違えばメモリ空間も違うので食い違いが生じる。
次に、送信したデータはプロシージャの終了までしか保持されない。
よってデータはプロシージャ内でコピーする必要がある。
同期の確実性が無い。対話形式の通信が難しい。

次にパイプという方法がある。
データの共有というよりはデータの通信に適した方法である。
サーバーとクライアントという考え方を用いる。
サーバーがパイプを作成しクライアントが個々で接続する形。
問題はパイプの作成が Windows95 や Windows98 で使用できない点。

サーバー側の設定は、

パイプの作成をする。名前は \\サーバー名\pipe\パイプ名 とする。

hPipe = CreateNamedPipe(
  "\\\\.\\pipe\\a_pipe",  // パイプの名前 '.' は自サーバーをあらわす
  PIPE_ACCESS_DUPLEX,  // 双方向通信
  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,  // バイト、待機モード
  1,  // 接続できる最大数
  1024,  // 出力バッファサイズ
  1024,  // 入力バッファサイズ
  100,  // クライアントがディフォルトの場合のタイムアウト時間
  NULL);  // セキュリティ

戻り値は失敗時に INVALID_HANDLE_VALUE となる。

接続待機は ConnectNamedPipe で行う。
第1引数はパイプのハンドル。第2引数はセキュリティで普通は NULL。
PIPE_WAIT の場合接続されるまで待機することになる。
読み書きは ReadFile と WriteFile で行うことができる。
この場合も PIPE_WAIT の場合は読み込むデータが来るまで待機となる。
書き込みの場合はファイルの扱いと同じく FlushFileBuffers で
バッファにデータを残さないように注意する必要がある。
接続を切る場合は DisconnectNamedPipe を呼ぶ。最後にハンドルを閉じる。

クライアント側の設定は、

パイプに繋ぐ場合は CreateFile を使う。
ファイル名のところで \\サーバー名\pipe\パイプ名 を指定する。
失敗時には INVALID_HANDLE_VALUE が返る。
ただこれでは接続エラーなのかサーバービジーなのかがわからないので、
GetLastError で ERROR_PIPE_BUSY が返るかどうかを調べる必要がある。
ビジーで接続できなかった場合には WaitNamedPipe で待つことができる。
NMPWAIT_WAIT_FOREVER で無限に待ち、
NMPWAIT_USE_DEFAULT_WAIT でサーバーのディフォルト時間を使う。
注意として WaitNamedPipe は待つだけのAPIということで、
接続には再び CreateFile を呼ぶ必要がある。
WaitNamedPipe で待ったが CreateFile する前に
他の接続にサーバーを取られてしまう可能性もある。
接続後は読み書きは ReadFile と WriteFile で行うことができる。
通信後はハンドルを閉じれば良い。

最後にソケットを使う方法もあります。
OSが違っても通信できるので通信手段としては最強ですが、
ここではやりません。

inserted by FC2 system