以前、同一マシン内での RPC について、名前付きパイプと LPC のそれぞれの方法で通信するクライアントとサーバーを作りました。このときは同期 RPC、すなわちクライアントがメソッドを呼び出すと、サーバー側での処理が終わるまで制御が返ってこない RPC でした。今回は非同期 RPC 通信についてプログラムを書いたので記事にします。

[Win32] [C++] Local RPC over Named Pipe and LPC
http://msmania.wordpress.com/2011/07/10/win32-c-local-rpc-over-named-pipe-and-lpc/

4 回に分けて書くことになりました。今回が #1 のインターフェース定義編です。

#1 - インターフェース定義編 
http://msmania.wordpress.com/2012/03/08/win32-c-asynchronous-rpc-with-io-completion-port/

#2 - RPC サーバー編 
http://msmania.wordpress.com/2012/03/08/win32-c-asynchronous-rpc-with-io-completion-port-2/

#3 - RPC クライアント編 
http://msmania.wordpress.com/2012/03/08/win32-c-asynchronous-rpc-with-io-completion-port-3/

#4 - ネットワーク キャプチャー編 
http://msmania.wordpress.com/2012/03/08/win32-c-asynchronous-rpc-with-io-completion-port-4/ 

非同期 RPC では、クライアントがメソッドを呼び出しても、RPC サーバーの処理に関係なく制御がすぐに返ってきます。このため、実際に RPC サーバーでメソッドの処理が終わったときにコールバックを受ける必要が出てきます。このときのコールバック方法には複数の選択肢があり、いずれかをクライアント側が提示することができます。正確には、メソッドを呼び出すときのパラメーターである RPC_ASYNC_STATE 構造体の RPC_NOTIFICATION_TYPES 列挙値で指定します。

  • コールバックなし
  • イベント オブジェクト
  • APC (Asynchronous Procedure Call)
  • I/O 完了ポート
  • ウィンドウ メッセージ
  • コールバック関数

種類が豊富ですね。APC は使ったことがないのであまり知りませんが、それ以外は何となく想像がつきます。
さて、この中で比較的実装が複雑になりそうな I/O 完了ポートを使ってサンプルプログラムを作ります。ちなみに、MSDN に載っている非同期 RPC のサンプルはイベント オブジェクトを使うものでした。

RPC_ASYNC_STATE structure
http://msdn.microsoft.com/en-us/library/windows/desktop/aa378490(v=vs.85).aspx

RPC_NOTIFICATION_TYPES enumeration
http://msdn.microsoft.com/en-us/library/windows/desktop/aa378638(v=vs.85).aspx

一つの記事で書くにはちょっと複雑なプログラムになってしまったので、まず最初にプログラムの全体像を紹介します。

プロジェクトのフォルダー構造は抜粋するとこんな感じです。
今回は GUI で書きました。64bit ネイティブでビルドしましたが、32bit でも普通にビルドできます。たぶん。

AsyncRpc 
│  AsyncCommon.cpp … クライアント/サーバー共通コード 
│  AsyncCommon.h 
│  
├─AsyncClient 
│      main.cpp 
│      AsyncClient.h 
│      AsyncClient.cpp 
│      resource.h 
│      AsyncClient.rc 
│              
├─AsyncServer 
│  │  main.cpp 
│  │  AsyncServer.h 
│  │  AsyncServer.cpp 
│  │  resource.h 
│  │  AsyncServer.rc 
│  │  
│  └─x64 
│      └─Debug 
│              AsyncClient.exe 
│              AsyncClient.pdb 
│              AsyncServer.exe 
│              AsyncServer.pdb 
│              
└─idl 
        pipo.idl … インターフェース定義関連 
        pipo.acf 
        pipo.h 
        pipo_c.c 
        pipo_s.c

1. インターフェース定義を作る (IDL, ACF ファイル)

まずは短いところから。IDL ファイルと ACF ファイルをテキスト エディターで書きます。ひな型の作成に uuidgen /i コマンドを使うこともできます。(前回の記事参照)

// 
// pipo.idl 
// 
// generated with ‘uuidgen /i /opipo.idl’ 
// compile with ‘midl pipo.idl’ 
// 
// http://msdn.microsoft.com/en-us/library/aa367088 
//

[ 
uuid(161b9ab8-1a96-40a6-bf8b-aa2d7ec94b6d), 
version(1.0) 
] 
interface pipo 
{ 
    void RpcSleep(int Duration); 
    void RpcSleepAsync(int Duration); 
    void Shutdown(); 
} 

IDL ファイルは普通ですね。ACF ファイルはこんな感じです。

// 
// pipo.acf 
// 
// http://msdn.microsoft.com/en-us/library/aa366717(v=VS.85).aspx 
// 
  
[ 
implicit_handle(handle_t pipo_IfHandle) 
] 
interface pipo 
{ 
    [async] RpcSleepAsync(); 
} 

非同期 RPC にしたい関数には、ACF ファイル内で [async] 属性を付けておきます。詳細はそれぞれのファイルの先頭に書いた MSDN のページを参考にして下さい。

ファイルが書けたら、Windows SDK に含まれる midl.exe で IDL ファイルをコンパイルします。 ACF ファイルは midl が自動的に読み込みます。

> midl pipo.idl 
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0555 
Copyright (c) Microsoft Corporation. All rights reserved. 
64 bit Processing .\pipo.idl 
pipo.idl 
64 bit Processing .\pipo.acf 
pipo.acf

これで、インターフェースについてのヘッダーとソース ファイルが自動生成されました。

後で書くプログラムの仕様上、クライアント用ソース ファイルの pipo_c.c に含まれるインターフェース ハンドルのグローバル変数を、NULL で初期化しておきます。

これが修正前。

/* Standard interface: pipo, ver. 1.0, 
   GUID={0x161b9ab8,0x1a96,0x40a6,{0xbf,0x8b,0xaa,0x2d,0x7e,0xc9,0x4b,0x6d}} */

handle_t pipo_IfHandle; 

修正後。

/* Standard interface: pipo, ver. 1.0, 
   GUID={0x161b9ab8,0x1a96,0x40a6,{0xbf,0x8b,0xaa,0x2d,0x7e,0xc9,0x4b,0x6d}} */

handle_t pipo_IfHandle= NULL; 

この記事はここまで。
次回は RPC サーバーを作ります。