EternalWindows
RPC / 概要と設定

何台ものコンピュータが相互に繋がっている環境において、 ある大規模な処理を1つのコンピュータ上で行うのは非効率であるといえます。 できることなら、全てのコンピュータに処理を分散させ、 各コンピュータのパフォーマンスを活かせるような実装にするべきといえるでしょう。 このようにすれば、1つのコンピュータに大きな負荷が掛かることもなく、 処理が完了するまでの時間も短くなることが期待できます。 このように処理を分散して実行することは、 DCE(Distributed Computing Environment)や分散コンピューティングと呼ばれ、 RPC(Remote Procedure Call)というネットワークAPIを呼び出すことで実現できます。 RPCには、OSF/DCEやSun RPCといういくつかの標準が存在していますが、 WindowsにおけるRPCの実装はOSF/DCEをベースにしています。

RPCはDCEを実現するための手段であると同時に、 ネットワーク通信を透過的に行うという側面も持っています。 たとえば、クライアントがサーバーにある処理を依頼するような場合、 本来ならばWinsockのようなネットワークAPIを呼び出して 処理内容を示すデータをサーバーに送信することになりますが、 RPCを使用すればこのような作業は完全に隠蔽されます。 クライアントは、目的の処理を実装したサーバーの関数を呼び出すだけで、 サーバーに処理を実行させることができるのです。

int nValue;

nValue = ServerFunction();

サーバーがServerFunctionという関数を実装し、これをクライアントに公開している場合、 クライアントはServerFunctionという関数を呼び出すことができます。 これにより、関数の処理はサーバー上で実行し、 クライアントは戻り値だけ受け取ることができるので、 クライアントのコンピュータに負荷が掛かることはなくなります。 RPCを利用することで、クライアントは直感的にサーバーの機能を利用することができるようになります。

さて、上記したServerFunctionですが、 本来ならばこのような呼び出しが成立するはずがないのは明白です。 ServerFunctionの実装はサーバーのアプリケーション内にあるわけですから、 これをクライアントが呼び出せるわけはありませんし、 コンパイル自体が失敗に終わることになるはずです。 それでは、どうすれば上記したコードを成立させることが可能になるのでしょうか。 答えは、スタブの利用にあります。 スタブの実体はソースファイルであり、 このソースファイルにはサーバーが公開している関数と同様のプロトタイプを持った関数が存在しています。 そして、それらの関数は、目的の関数をサーバー自身に呼び出させるための命令を、 RPCランタイムライブラリを通じてサーバーに送信しています。 つまり、クライアントが呼び出しているServerFunctionというのは、 正確にはサーバーに実装されているServerFunctionではなく、 スタブのServerFunctionということになります。

図が示すように、クライアントの呼び出しはスタブを通じてrpcrt4.dll(RPCランタイムライブラリ)に渡り、 RPCランタイムライブラリはサーバー上のRPCランタイムライブラリと通信します。 そして、サーバー上のRPCランタイムライブラリは、スタブの関数を呼び出し、 スタブの関数はサーバーの関数を呼び出します。 これで、サーバー自身が関数の処理を行えるようになります。 サーバーの関数が制御を返せば、スタブの関数も制御を返し、 最終的にクライアントへサーバーの関数の戻り値が届くことになります。 クライアントとサーバーが使うスタブは互いに異なります。

これまでの話により、クライアントがサーバーの関数を呼び出すことのできる仕組みは分かりましたが、 それではスタブはどのようにして作成すればよいのでしょうか。 スタブの正体はソースファイルですが、 実際に開発者がこのようなソースファイルを作成してコードを記述することはありません。 開発者が作成するのはIDLファイルであり、これをIDLコンパイラに指定することで、 適切なソースファイル(スタブ)とヘッダーファイルを作成することになります。 IDL(Interface Description Language)とは、インターフェース定義言語のことであり、 名前の通り関数のプロトタイプなどを定義します。 何故、このような専用の言語を使用して関数を定義しなければならないのかというと、 1つのIDLファイルから任意のプログラミング言語で記述されたスタブを作成できるようにするためです。 たとえば、C言語に対応しているIDLコンパイラにIDLファイルを指定すれば、 C言語で実装されたスタブが作成され、C#に対応しているIDLコンパイラに指定すれば、 C#で実装されたスタブが作成されるはずです。 これにより、異なるプログラミング言語で作成されたアプリケーションでも、 スタブを介してRPC通信ができるという利点が生まれます。 次に、IDLファイルの一例を示します。

[
	uuid (3b5c8791-ada4-4f39-9644-9edd05e199ac),
	version (1.0)
]
interface sample
{
	int ServerFunction(void);
}

IDLファイルの内容は、ヘッダとボディに分かれます。 []の範囲がヘッダであり、ここにはUUIDやバージョンなどを専用のキーワードで定義します。 一方、{}の範囲がボディであり、ここにはクライアントに公開する関数を定義します。 上記ではServerFunctionという関数を定義しているため、 クライアントはこのServerFunctionという関数を呼び出すことができます。 また、サーバーはServerFunctionを実装しておく必要があります。 IDLファイルの関数定義は、C言語の構文と大変よく似ていますが、 それはあくまで似ているだけであり、上記コードはIDLという言語で記述されたものです。

IDLファイルを作成したら、次はこれをコンパイルしなければなりません。 Visual StudioにはMIDLというIDLコンパイラが付属しており、 このコンパイラはIDLファイルからC言語のソースファイルとヘッダーファイルを作成します。 IDLファイルをコンパイルするには、まずIDLファイルをプロジェクトに追加します。 「プロジェクト」から「新しい項目の追加」を選択して、次のダイアログを表示してください。

ここで、MIDLファイルを選択して「追加」を押せば、 IDLファイルがプロジェクトに追加されることになります。 Visual Studioのバージョンによっては、MIDLのテンプレートが存在しないことがありますが、 その場合はメモ帳などで拡張子が.idlであるファイルを作成し、 これを「既存項目の追加」で選択すればよいでしょう。 また、ソリューションエクスプローラー上にドラッグ&ドロップしても構いません。 プロジェクトへの追加が終了すれば、先に示したコードをIDLファイルに貼り付けます。

これにより、IDLファイルをコンパイルすることができるようになります。 ただし、その前にIDLファイルと同じ名前を持ったACF(Application Configuration File)ファイルを、 IDLファイルと同じフォルダに作成しておいてください。 このファイルは、コンパイル時にMIDLによって確認されます。

このように、ACFファイルの名前はIDLファイルと同一にしておきます。 そして、次のコードをファイルに貼り付けてください。

[
	implicit_handle(handle_t hBinding)
]
interface sample
{
}

ACFファイルもIDLファイルと同じように、ヘッダとボディで定義するデータを分割することになります。 ACFファイルは、バインディングの方法やインターフェースの特性について定義します。 ACFファイルは、プロジェクトに追加することはありません。 準備が整ったら、いつものようにビルドを実行することになります。 これが成功した場合は、次に示す3つのファイルが作成されることになります。

IDLファイルの名前をsampleとしていたため、作成されたファイルにsampleという文字列が含まれています。 sample_c.cは、クライアントが使用するスタブになります。 一方、sample_s.cはサーバーが使用するスタブになります。 sample_h.hは、クライアントとサーバーが使用するヘッダーファイルになります。 次に、クライアントアプリケーションをコンパイルする例を示します。

クライアントアプリケーションを開発するため、sample_c.cをプロジェクトに追加しています。 また、sample_h.hもヘッダーファイルに追加します。 これらの追加は、「既存項目の追加」から行うことができます。 sample.cは、アプリケーションのエントリポイントなどを記述する通常のソースファイルです。 実際の開発ではここに、サーバーの関数を呼び出すコードを記述することになるでしょう。 また、サーバーの関数を呼び出すにはいくつかのRPC関数を呼び出すことになるため、 rpc.hのインクルードとrpcrt4.libへのリンクが必要になります。 サーバーが実装している関数のプロトタイプはsample_h.hに定義されているため、 これも忘れずにインクルードしておくことになります。

midl_user_allocateやmidl_user_freeは、スタブが呼び出す関数です。 スタブはメモリを確保する段階になるとmidl_user_allocateを呼び出すため、 ここにはメモリを確保するコードを記述しておく必要があります。 また、midl_user_freeはメモリが不要になった場合にスタブが呼び出す関数で、 ここにはメモリを開放するコードを記述しておく必要があります。 確保や破棄に使用する関数に特に決まりはないと思われますが、 mallocとfreeを使用するのが習慣となっているようです。 次に、サーバーアプリケーションをコンパイルする例を示します。

サーバーアプリケーションの開発をする場合は、 sample_c.cではなくsample_s.cをプロジェクトに追加し、 クライアントに公開する関数を実装しておきます。 IDLファイルにはServerFunctionを記述していましたから、 サーバーはServerFunctionを実装する必要があります。 この例では、単に1を返すという意味のない実装になっていますが、 実際の開発ではクライアントの目的に沿った処理を行うことになります。 クライアントとサーバーが使用するIDLファイルの内容は同一でなければならないため、 片方を変更した場合はもう片方も変更する必要がある点に注意してください。

uuidgen.exeの利用

IDLファイルに指定するUUIDキーワードには、 インターフェースを一意に識別するためにUUIDを指定することになります。 UUIDは、UuidCreateという関数を呼び出すことで作成できるため、 これを呼び出して値を保存するアプリケーションを開発しても構いませんが、 ここではuuidgen.exeを利用する方法について説明します。 uuidgen.exeは、UUIDを作成するアプリケーションであり、 Windows SDKをインストールしている場合は次のパスに存在していると思われます。

C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin

uuidgen.exeは、コンソールアプリケーションであるため、 コマンドプロント上で次の内容を入力します。

cd C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin

これにより、指定したパスへ移動できたことになります。 次に、uuidgenを実行するため、uuidgenを入力します。

uuidgen

これにより、uuidgen.exeによって作成されたUUIDが表示されるため、 後はこれをコピーしてIDLファイルに張り付ければよいことになります。 また、UUIDをファイルとして保存したい場合は、uuidgen -oc:\sample.txtのよう入力します。 ただし、この場合はCドライブ直下に予めsample.txtが存在している必要があります。 uuidgen.exeは、新たなRPCアプリケーションを開発する度に実行することになります。



戻る