次の方法で共有


応用リモート処理 (Remoting)

Rockford Lhotka
Magenic Technologies

May 11, 2003

概要 : この記事では、クライアント/サーバー通信およびピア ツー ピアの通信でのリモート処理 (Remoting) の基本的な使い方を調査し、クライアント ワークステーションとサーバーの COM+ で実行されている ServicedComponent との間の通信にリモート処理を使用する方法を説明します。

サンプル ファイル (vbAppliedRemoting.exe) のダウンロード

ほぼ 18 か月前のことになります。 このコラムに Web サービスとリモート処理を比較する記事 (「Remoting and XML Web Services in Visual Basic .NET」 (英語)) を書きました。 それ以来、数多くの人々が .NET Framework に基づくアプリケーションを開発するようになり、 その結果、リモート処理の役割とそれを使用する方法に関する質問の数が増えてきました。 COM+ の使用や、 ネットワーク間で ServicedComponent (COM+ で実行されるコンポーネント) を呼び出すクライアント アプリケーションを作成する方法など、 関連する質問もあります。

Web 上にはリモート処理を扱ったさまざまな記事があり、 その件に関する書籍も数冊ありますが、 このコラムでこのトピックを再度取り上げることに価値があると考えました。 この記事では、クライアント/サーバー通信およびピア ツー ピアの通信でのリモート処理の基本的な使い方を調査します。 また、クライアント ワークステーションとサーバーの COM+ で実行されている ServicedComponent との間の通信にリモート処理を使用する方法も調べます。

いろいろな意味で、 リモート処理は DCOM (分散 COM) の論理的な後継機能です。 リモート処理は、DCOM とほぼ同様の機能のセットを提供すると同時に、 Web サービスに似た機能のセットを提供します。 大まかには、リモート処理は両方の環境の最善の機能を提供します。

リモート処理を使用することにより、 以下に示すようなさまざまなクラスのネットワーク ベースのアプリケーション (一般的には、かっこ内に示した種類の Visual Studio® .NET アプリケーション) を作成できます。

クラス クライアント モード サーバー モード
クライアント/サーバー インテリジェント、状態ありまたは状態なし
(Windows フォームまたは Web フォーム)
状態なし、サービスを提供
(ASP.NET)
オートメーション インテリジェント、状態ありまたは状態なし
(Windows フォームまたは Web フォーム)
ActiveX EXE にやや似ています
(Windows フォーム)
サーバー ベース インテリジェント、最小限の状態
(Windows フォームまたは Web フォーム)
状態あり、データを管理し、サービスを提供
(Windows サービス)
ピア ツー ピア インテリジェント、状態あり
(Windows フォーム)
インテリジェント、状態あり
(Windows フォーム)

ここで気付く 1 つの重要な点は、 どの場合でもクライアントがインテリジェントであることです。 ブラウザ ベースのクライアントは、 リモート処理を使用して、直接オブジェクトと相互作用することはできません。 ブラウザは、ASP.NET では Web フォームと相互作用できるので、 その Web フォームにリモート オブジェクトと相互作用するコードを含めることができます。 このような場合、Web フォームがインテリジェント クライアントになるので、 既にお分かりのように、 Web フォームでリモート処理を使用するコードは、 Windows フォームで作成したものと等価になります。

クライアント/サーバーの事例では、 以前に多くの人々が DCOM と MTS または COM+ を使用していたのと非常によく似ています。 クライアントは、おそらく、 必要に応じてサーバー上の状態なしオブジェクトを呼び出している Windows フォーム アプリケーション、 または Web フォーム アプリケーションになります。 Visual Basic® .NET アプリケーションでは、 これらのサーバー側オブジェクトは EnterpriseServices の機能を必要とするかどうかによって、 COM+ で実行されたり、されなかったりします (EnterpriseServices の詳細については、「COM+ よ、汝はいずこに」を参照してください)。

オートメーションの事例は、 以前のバージョンの Visual Basic で ActiveX® EXE を使用していたのとやや似ています。 この場合、"サーバー" は実際には Windows フォーム アプリケーションになります。 ユーザーはアプリケーションと直接相互作用したり、しなかったりしますが、 その他のクライアント アプリケーションは、 プログラムからリモート処理を使用して、 アプリケーションと相互作用できます。 このようなクライアント アプリケーションは、同じコンピュータで実行されることもあれば、 ネットワーク経由で別のコンピュータで実行されることもあります。

サーバー ベースの事例は、 サーバー上のオブジェクトに状態があり、長期間存続する可能性があることを除けば、 クライアント/サーバーの事例にやや似ています。 この独特のアーキテクチャはここ数年人気がありましたが、 依然として、大規模で、状態を持ち、 プロセッサを集中的に使用するオブジェクトをサーバー上で実行する必要のあるアプリケーションが存在します。 リモート処理は、以前の DCOM と同様に、 必要に応じてこの種のアーキテクチャをサポートします。

ピア ツー ピアの事例は、 各クライアントがサーバーにもなる事例です。 この場合、クライアントは他のクライアントにオブジェクトを作成でき、 他のクライアントのオブジェクトと相互作用できます。 各クライアントは、リモート オブジェクトを呼び出せるだけでなく、 リモート処理を使用して独自のオブジェクトを公開できます。

ネットワーク処理

各アプリケーション事例の実装を調べる前に、 ネットワーク処理、IP アドレス、 およびそれらがリモート処理にどのように影響するかを簡単に説明しておく必要があります。 クライアントとサーバー間に存在するネットワークが、 リモート処理がどのようにニーズを満たすか、 またはニーズを満たすかどうかについて、 深刻な影響を及ぼす可能性があります。

リモート処理は、 クライアントからサーバー (または、サーバーからクライアント) の通信に標準の TCP ソケットを使用します。 つまり、クライアントは特定のポートでサーバーの IP アドレス (または、IP アドレスに変換されるドメイン名) を使って、 サーバーへの接続を開きます。 明らかに、クライアントが使用するポートは、サーバーが受信待ちするポートと同じである必要があります。

サーバーが ASP.NET でホストされている場合は、 サーバーが受信待ちするポートは、総じて、Web サーバーの IIS により定義されます。 既定のポートはポート 80 です。 これは、リモート処理が Web サービスやその他の HTTP トラフィックとまったく同様にポート 80 を容易に使用できることを意味します。 サーバーが Windows フォーム アプリケーション、Windows サービス、 またはその他の種類のアプリケーションでホストされている場合は、 ポートを指定するのはサーバー プログラマの役割です。 つまり、ASP.NET 以外のホストでは、リモート処理は事実上任意の未使用ポートで受信待ちできます。

このことはすべての点において、リモート処理がファイアウォールと相性がよいことを示しています。 サーバーとの通信にリモート処理を使用するクライアントでは、ポートを 1 つだけ開く必要がります。 これとは対照的に DCOM では数多くのポートを開く必要があり、 インターネット セキュリティの利点では、 リモート処理が DCOM よりも優っていることが明らかになります。

また、リモート処理は "接続なし" です。 つまり、クライアントからサーバー (または、サーバーからクライアント) への各メソッド呼び出しがネットワーク経由で接続を行い、 呼び出しを行った後、その接続を閉じます。

**注意   ** リモート処理は論理的な意味合いのみで接続なしということになります。 実際には、パフォーマンスを向上するために、 背後でプールされることがあります。 しかし、このように接続がプールされる場合でも、 ここで説明している問題は極めて重要になります。

つまり、クライアントが TCP/IP ネットワーク経由でサーバーに到達できる場合のみ、 クライアントはサーバー側オブジェクトのメソッドを呼び出すことができます。 これは、クライアントがサーバーの IP アドレスを持ち、 その IP アドレスが到達可能であることを意味します。

Dd314001.vbnet05272003-fig01(ja-jp,MSDN.10).gif
図 1. クライアントがネットワーク経由でサーバーに到達可能

ここで考えられる唯一の点は、 クライアントがサーバーに ping できなければ、リモート処理は機能しないということです。 サーバーは ping を拒否する場合があるので、 このテストは常に正確ではありませんが、 多くの場合に有効なテストです。

また、クライアントを呼び出すサーバーの場合、 このことはクライアントがサーバーから到達可能な IP アドレスを持つ必要があることを意味します。 サーバー側オブジェクトが、イベントを発生するか、 クライアントが提供するデリゲートを呼び出すときは常に、 サーバーがクライアントを呼び出すでしょう。 このことは、サーバー側オブジェクトの事例やピア ツー ピアの事例では、 特に一般的になります。

繰り返しになりますが、 ここで考えられる唯一の点は、 サーバーがクライアントに ping できなければ、 リモート処理はサーバーがイベントを発生することや、 クライアント側オブジェクトのメソッドを呼び出すことを許可しないということです。

Dd314001.vbnet05272003-fig02(ja-jp,MSDN.10).gif
図 2. サーバーがネットワーク経由でクライアントに到達可能

サーバーは、 クライアントがサーバーを呼び出すときに、 自動的にクライアントの IP アドレスを取得しますが、 多くの場合クライアント コンピュータは仮想 IP アドレスまたはルーティングできない IP アドレスを所持しています。 このことは、クライアントとサーバーの間にファイアウォールや NAT ルーターが存在するときに生じます。 このような場合、 図 3 に示すように、 一般的にサーバーの IP アドレスは既知で固定ですが、 クライアントの IP アドレスは仮想で、サーバーからは到達できないことになります。

Dd314001.vbnet05272003-fig03(ja-jp,MSDN.10).gif
図 3. サーバーはクライアントの仮想 IP アドレスに到達不可能

この問題の公式のソリューションはありません。 主な選択肢は、 接続ベースのリモート処理チャネルを作成または検索するか、 TCP ソケットそのものを使用するプログラミングを行ってリモート処理を避けることです。 http://www.ingorammer.com/Software/OpenSourceRemoting/OpenSourceMain.html (英語) に、利用できるオープン ソースの接続ベース TCP チャネルが 1 つあります。

これで、 ネットワーク処理がリモート処理の使用にどのように影響する可能性があるかを基本的に理解したことになります。 では、リモート処理の各事例を 1 つずつ見ていくことにしましょう。

クライアント/サーバー リモート処理

クライアント/サーバーの事例は、 クライアント (一般的には、Windows フォームまたは Web フォームのいずれか) が、 ネットワーク経由でサーバー上で実行されている何らかのコードを呼び出す必要のある事例です。 このサーバー側コードは COM+ になる場合も、ならない場合もあるので、 両方の状況を見ていくことにしましょう。 幸いなことに、両者は非常によく似ています。

この種の状況では、 サーバー側コードを ASP.NET でホストするのが最も簡単です。 ASP.NET は、構成や管理を提供するだけでなく、 組み込みのセキュリティ機能を提供します。

サーバー側コード自体は、 クラス ライブラリ プロジェクトのクラスになるでしょう。 リモート処理経由で利用できるようにし、 サーバー上でも実行されるようにするには、 クラスを MarshalByRefObject から継承する必要があります。 また、EnterpriseServices の機能が必要な場合は、 クラスを ServicedComponent (これ自体は MarshalByRefObject から継承します) から継承できます。

標準のサーバー側コード (COM+ を使用しない) 場合は、クラスは次のようになります。


Public Class ServerSide
  Inherits MarshalByRefObject

End Class

COM+ で実行する必要のあるサーバー側コードの場合は、 クラスは次のようになります。


<Transaction(TransactionOption.Required), _
 EventTrackingEnabled(True)> _
Public Class ServerInComPlus
  Inherits ServicedComponent

End Class

EnterpriseServices を使用するバージョンでは、 使用する特定の EnterpriseServices 機能を示すために、 クラスに属性も適用することに注意してください。 この場合、分散 2 フェーズ トランザクションを使用し、 情報を提供しているので、 コンポーネント サービス コンソールでオブジェクトの状態を確認できます。

また、ServicedComponent を含むアセンブリは System.EnterpriseServices.dll を参照し、 厳密名を持つ必要があることを覚えておいてください。 つまり、AssemblyInfo.vb ファイルは、 次のように COM+ アプリケーションを構成するエントリだけでなく、 キー ファイルへのエントリ ポイントを持つ必要があります。


' 厳密名
<Assembly: AssemblyKeyFile("c:\mykey.snk")> 

' EnterpriseServices の属性
<Assembly: ApplicationName("MyComPlusService")> 
<Assembly: Description("My service")> 
<Assembly: ApplicationActivation(ActivationOption.Library)> 

これらのいずれのクラスも、 すべてのパブリック メソッドをリモート処理経由で利用できるようにします。 この記事のコードでは、 これがどのように機能するかを例示するために、 これらの各クラスが UpdateDB メソッドを持ちます。 どちらの場合もトランザクション処理になります。 つまり、通常のクラスは ADO.NET トランザクションに依存し、 ServicedComponent は COM+ トランザクションに依存します。

この例では、1 つのデータベースを更新するだけなので、 ADO.NET トランザクションは完全に受け入れ可能です。 この場合、ADO.NET バージョンは COM+ バージョンの約 2 倍高速に実行されることに注意することが重要です。 トランザクションのパフォーマンス特性の比較については、 https://msdn.microsoft.com/architecture/default.aspx?pull=/library/en-us/dnbda/html/bdadotnetarch13.asp を参照してください。

これで、標準のサーバー側コードと COM+ サーバー側コードの両方を作成する方法を理解したことになるので、 リモート処理経由でコードをクライアントに公開してみましょう。 これを行うための最も容易かつ一般的に最適な方法は、Visual Studio .NET で空の ASP.NET プロジェクトを作成することです。 ダウンロードしたコードでは、vbAppliedRemotingService プロジェクトを参照してください。

プロジェクトは作成したサーバー側コードをホスティングすることになるので、 作成したクラス ライブラリ プロジェクトを参照する必要があります。 その結果、ソリューションがビルドされるときに、 Visual Studio .NET が自動的にコンパイル済み DLL を bin ディレクトリにコピーすることになります。 リモート処理を利用できるようにするためには、 作成した DLL が仮想ルートの bin ディレクトリに存在する必要があります。

web.config ファイルを ASP.NET プロジェクトに追加します。 これは、クライアント要求を受信待ちするようにリモート処理を構成する構成ファイルです。 ここでは、以下のようにリモート処理を構成するセクションを web.config で定義します。


   <system.runtime.remoting>
      <application>
         <service>
            <wellknown 
               mode="SingleCall" 
               objectUri="ServerSide.rem"
               type="RegularLibrary.ServerSide, RegularLibrary" />
            <wellknown 
               mode="SingleCall" 
               objectUri="ServerInComPlus.rem"
               type="ComPlusLibrary.ServerInComPlus, ComPlusLibrary" />
         </service>
      </application>
   </system.runtime.remoting>    

クラスごとに、wellknown エントリ ポイントを定義している方法に注目してください。

このコードは状態なしになるようにデザインされているので、 SingleCall モードを使用します。 つまり、 クライアントからの各メソッド呼び出しは、 そのメソッドだけのためにサーバー上にオブジェクトを作成することになります。 クライアントからのその後のメソッド呼び出しでは、そのオブジェクトは再利用されません。 これは、Web サービスを使用した場合に得られる動作と同じです。 さらに、DCOM および COM+ で JIT アクティベーションを使用するときと同じ動作です。

この構成では、クライアントがオブジェクトの呼び出しに使用できる次の 2 つの URL が定義されることになりました。


https://localhost/vbAppliedRemotingService/ServerSide.rem
https://localhost/vbAppliedRemotingService/ServerInComPlus.rem

URL に ?wsdl を付加し、ブラウザでその URL にナビゲートすることによって、 これらの URL をテストできます。 ブラウザに表示される結果は、そのサービスを説明する XML になります。

クリックすると拡大表示されます
図 4. リモート オブジェクトの WSDL

クライアントから ServerInComPlus を使用する前に、 アセンブリを COM+ に登録する必要があります。 標準の Visual Basic .NET クラスの ServerSide はすぐに使えるようになっていますが、 ServicedComponent はまず登録する必要があります。

.NET Framework は、アセンブリが最初に呼び出されるときに、 そのアセンブリを自動的に COM+ に登録しようとします。 ここでの課題は、サーバー側コードが ASP.NET ユーザー アカウントで実行されていて、 そのアカウントがアセンブリを登録するセキュリティ権限を持っていないことです。 つまり、任意のクライアントがそのサービスの呼び出しを試みる前に、 手動でアセンブリを登録する必要があります。

regsvcs.exe コマンド ライン ユーティリティを使用して、 アセンブリを COM+ に登録します。 [Visual Studio .NET コマンド プロンプト] を開き、ASP.NET 仮想ルートの bin ディレクトリに移動します。 その後、以下のように入力して、ユーティリティを実行します。


> regsvcs ComPlusLibrary.dll

この操作を完了すると、 クライアント アプリケーションから呼び出されたときに、ASP.NET が COM+ 内の ServicedComponent を起動できます。

リモート処理されるサービスのクライアントは、 通常、Windows フォーム アプリケーションか、Web フォーム アプリケーションのいずれかです。 どちらの場合でも、 クライアント アプリケーションはリモート処理経由でサーバー側オブジェクトを使用するために、 アプリケーション自体を構成する必要があります。 このような構成作業は、アプリケーション構成ファイルまたはコード内で行います。 コードの方がより管理された環境を提供するので、 私は、通常、コードでクライアントを構成します。

リモート処理は、 クライアント アプリケーションが起動されるときに一度構成される必要があります。 Windows フォーム アプリケーションでは、 通常、メイン フォームの Form_Load イベントで構成を行います。 Web フォーム アプリケーションでは、 通常、Global.asax の Application_Start イベントで構成を行います。 どちらの場合も、コードは同じになります。 まず、バイナリ フォーマッタを使用するように HTTP チャネルを構成します。


    ' 以下は、高速バイナリ フォーマッタを使用するように HTTP チャネルを構成します。
    Dim properties As New Hashtable()
    properties("name") = "HttpBinary"
    ChannelServices.RegisterChannel(New HttpChannel(Nothing, _
      New BinaryClientFormatterSinkProvider(), Nothing))

これは、BinaryFormatter が既定の SoapFormatter よりも高速で、 低バンド幅を使用するためです。 ネットワーク間で同じデータを転送する場合、 BinaryFormatter が生成するバイト数は SoapFormatter が生成するバイト数の約 30 % です。 さらに、データを XML などのテキスト表記ではなく、バイナリ形式に変換して送信後、 受け取り側でバイナリからデータに戻すので、CPU の占有率がはるかに低くなります。

HTTP チャネルを構成した後は、 リモート処理経由で呼び出されるクラスを指定します。


    ' 以下はリモート処理アクセス用の構成です。
    RemotingConfiguration.RegisterWellKnownClientType( _
      GetType(ComPlusLibrary.ServerInComPlus), _
      "https://localhost/vbAppliedRemotingService/ServerInComPlus.rem")
    RemotingConfiguration.RegisterWellKnownClientType( _
      GetType(RegularLibrary.ServerSide), _
      "https://localhost/vbAppliedRemotingService/ServerSide.rem")

ここから先のクライアント コードで ComPlusLibrary.ServerInComPlus または RegularLibrary.ServerSide のいずれかのインスタンスを作成すると、 サーバー上にオブジェクトを作成するために、 リモート処理を自動的に使用するトリガになります。

つまり、標準のコンポーネントを呼び出すために、 以下のようなコードを作成できます。


    Dim svc As New RegularLibrary.ServerSide()
    svc.UpdateDB()

ここには、特殊なコードは存在しないことがわかります。 リモート処理サブシステムが既に構成済みなので、 ServerSide オブジェクトを作成する試みが自動的にリモート処理にルーティングされます。 たとえば、UpdateDB を呼び出すと、 サーバー上に ServerSide オブジェクトが作成され、 メソッドが呼び出されて、結果が返されます。 その後、そのオブジェクトが破棄されます。

以下のように ServicedComponent を呼び出しても同じことが行われます。


    Dim svc As New ComPlusLibrary.ServerInComPlus()
    svc.UpdateDB()

これは、このコードがサーバーの COM+ 内部で実行されることを除けば、 基本的な処理は同じです。

サーバー側コードが単純な Visual Basic .NET オブジェクトか、 COM+ 内で実行される ServicedComponent のいずれかにかかわらず、 リモート処理がクライアント/サーバー環境にとって適切なメカニズムを提供します。

オートメーション

Microsoft Office は、1990 年代半ばに OLE オートメーションの概念を導入しました。 OLE オートメーションでは、COM を使用するクライアント アプリケーションを作成して、 Word や Excel などの他の "サーバー" アプリケーション内のオブジェクトを呼び出すことができます。 サーバー アプリケーションがまだ実行されていない場合は、 OLE オートメーションが独自のプロセス内で自動的にそのサーバー アプリケーションを起動します。

その後の Visual Basic 製品では、ActiveX EXE の形式で独自の "サーバー" アプリケーションを作成できるようにしました。 その後、COM を使用するクライアント アプリケーションを作成して、 ActiveX EXE 内のオブジェクトを呼び出すことができます。

Visual Basic .NET には直接 ActiveX EXE に相当するものはありません。 また、リモート処理は、OLE オートメーションを複製するために必要な機能を完全に提供しているわけでもありません。 リモート処理を使用することにより、サーバー アプリケーション内のオブジェクトを呼び出すクライアント アプリケーションを作成でき、 オブジェクトを公開するサーバー アプリケーションを作成できます。 ただし、サーバー アプリケーションが、まだ実行されていない場合に、 クライアントがそのサーバー アプリケーションを起動するメカニズムはありません。 また、クライアントがサーバーが受信待ちしている IP ポートを自動的に見つけ出すメカニズムもありません。

サーバー アプリケーションは、一般的には、 リモート処理のリスナとして構成される Windows フォーム アプリケーションです。 つまり、サーバー アプリケーションは、 クライアント アプリケーションが使用する 1 つ以上のクラスを公開します。 サーバー アプリケーションが公開するクラスは、 独立したクラス ライブラリ プロジェクトに配置する必要があります。 サーバー アプリケーションがこれらのクラスを参照する必要があるだけでなく、 すべてのクライアント アプリケーションも同様にこれらのクラスを参照する必要があるので、 このことが重要になります。 Visual Basic 6.0 で COM EXE を参照できるのとは異なり、 Visual Basic .NET では DLL のみを参照できます。

また、サーバー アプリケーションの作成は、 すべてのリモート処理呼び出しがバックグラウンド スレッドで実行されるという点で、 複雑になります。 バックグラウンド スレッドで実行されるコードは、 フォームや TextBox コントロールなどのすべての Windows フォーム UI コンポーネントと直接相互作用できません。 この問題とこの問題の 1 つのソリューションを、 以前に「Implementing a Background Process in Visual Basic .NET」 (英語) で説明しました。

サーバーの完全なコードは、 ダウンロードの AutomationServer プロジェクトにあります。 重要な点は、以下のようにアプリケーションの開始時にメイン フォームでリモート処理を構成することです。


    ' アプリケーション名 (仮想ルート名) を設定します。
    RemotingConfiguration.ApplicationName = mname

    ' クライアントを受信待ちするチャネルをセットアップします。
    ChannelServices.RegisterChannel(New TcpServerChannel(mPort))

    ' クラスを登録します。
    RemotingConfiguration.RegisterActivatedServiceType( _
      GetType(AutomationLibrary.Application))

ここでは、アプリケーション名とポートを指定します。 アプリケーション名とポートは、 サーバー アプリケーションとの相互作用に使用する URL クライアントを定義します。 これは、プロジェクトが System.Runtime.Remoting.dll アセンブリを参照することを意味することに注意してください。 この例では、URL は次のようになります。


tcp://localhost:15123/AutomationServer

このサーバーは、HttpServerChannel ではなく、TcpServerChannel を使用して受信待ちするので、 tcp:// プロトコルを使用しているのがわかります。

また、ここでは well-known 型ではなく、activated 型を登録していることに注意することが重要です。 activated 型は状態を持ち、長期間存続します。 クライアントは同じアクティブ オブジェクトに対して多くのメソッド呼び出しを行うことができます。 これは、ActiveX EXE の COM オブジェクトから得られる動作の形式です。

また、コードでは、 クライアントのメソッド呼び出しを安全に行い、 メソッド呼び出しが UI と安全に相互作用するために、 フォーム上でデリゲートと Invoke メソッドを使用します。 特に、SafePrint メソッドはクライアントの要求にサービスを提供するためにバックグラウンド スレッドで実行され、 さらに、フォームの Invoke メソッドを使用して、その呼び出しを UI スレッドに転送します。 UI スレッドでは、安全に表示を更新できます。


  Public Sub SafePrint(ByVal Text As String)

    Dim del As New PrintDelegate(AddressOf Print)

    Me.Invoke(del, New Object() {Text})

  End Sub

クライアントが直接呼び出す実際の Application クラスは、 デリゲートを使用して、この SafePrint メソッドへの参照を保持します。 UI を更新するときに、Application オブジェクトがこのデリゲートを呼び出します。 その結果、SafePrint メソッドが呼び出されます。 たとえば、クライアントが以下の SayHi メソッドを呼び出す場合は、 UI にテキストを出力するために、デリゲートが呼び出されます。


  Public Sub SayHi(ByVal From As String)

    mPrint.Invoke(From & " says hi")

  End Sub

また、AutomationClient も Windows フォーム アプリケーションです。 AutomationLibrary アセンブリへの参照を所持しているので、 Application クラスにアクセスできます。 また、System.Runtime.Remoting.dll も参照しているので、 リモート処理も使用できます。

一般的に、アプリケーションの開始時に最初に行うのは、 リモート処理を構成することです。


    ' 次のコードはリモート処理アクセスを構成します。
    RemotingConfiguration.RegisterActivatedClientType( _
      GetType(AutomationLibrary.Application), _
      "tcp://localhost:15123/AutomationServer")

これで、AutomationLibrary.Application がこの URL で見つかる activated 型であることを認識するようにリモート処理を構成しました。 このコードを実行するときに、サーバー アプリケーションが実行されていないと、 実行時例外が発生します。 これが、ActiveX EXE 環境との重要な違いになります。 ActiveX EXE 環境では、サーバーが起動されていない場合は、自動的に起動されます。

さらに、ここで使用するポート番号は、 サーバー アプリケーションが受信待ちするポート番号と一致している必要があることに注意してください。 両者が一致していないと、クライアントはサーバーを見つけられず、 サーバーが起動されていない場合と同じ実行時例外が発生します。

一旦、リモート処理が構成されてしまえば、サーバー アプリケーションとの相互作用は簡単です。 以下のように、単純に Application クラスのインスタンスを作成し、 そのインスタンスのメソッドを呼び出します。


    mApp = New AutomationLibrary.Application()
    mApp.SayHi(txtName.Text)
    mApp.SayBye(txtName.Text)

各メソッド呼び出しは、サーバー アプリケーションの "同じオブジェクト" に対して行われることを覚えておいてください。 これは OLE オートメーションとまったく同様に機能し、 サーバー アプリケーションのオブジェクトは状態を持ち、 長期間存続します。

このアプリケーションは単にサーバー アプリケーションのフォームにテキストを表示するだけですが、 もっと洗練されたオートメーション機能を実行する新たなメソッドを追加することにより拡張できます。 サーバー アプリケーションの機能全体を操作するプログラム インターフェイスを備えたクライアント アプリケーションを提供することも簡単にできます。

サーバー ベース オブジェクト

場合によっては、ある期間サーバーの重要な状態をアプリケーションで管理する必要がある場合があります。 この場合、状態なしのサーバー側オブジェクトに依存するモデルは理想的ではありません。 代わりに、状態ありのサーバー側オブジェクトを持つモデルが必要になります。

この場合、リモート処理は 2 つの選択肢を提供します。 最初の選択肢は最も一般的なもので、 上記のオートメーションの事例で行ったように、 アクティブ オブジェクトを使用することです。 もう 1 つの可能性は、サーバーで "シングルトン" オブジェクトを使用することです。

シングルトン オブジェクトはサーバー上に存在し、 すべてのクライアントにより共有されます。 その名前が示すように、 このようなオブジェクトは一度に 1 つだけ存在します。 これとは対照的に、アクティブ オブジェクトでは、 このようなオブジェクトが多数存在し、 オブジェクトを作成したクライアントに結び付けられます。

アクティブ オブジェクトはあるレベルの分離を提供し、 マルチ スレッド処理の問題点を最小にするので、 一般的には扱いが容易です。 各オブジェクトが特定のクライアントに結び付けられているので、 複数のクライアントが同じオブジェクトのデータと相互作用することについて注意する必要はありません。 クライアント アプリケーションがマルチ スレッド化されていない場合は、 オブジェクト内で複数のスレッドが同時に実行されることに関して注意する必要はありません。

シングルトン オブジェクトはすべてのクライアントに共有されるので、 分離の意味合いはまったくありません。 これは、異なるクライアントからの複数の呼び出しが、 そのオブジェクト内で、同時に、複数のスレッドで実行される場合がある点で複雑になります。 独自のコード内に分離を実装し、これらのスレッドを管理するのは、 開発者の役割です。

どちらの場合でも、 サーバーはサーバー側オブジェクトが常駐できる一貫した Windows プロセスを提供する必要があります。 ASP.NET は状態ありのオブジェクトのリモート処理ホストとして動作できますが、 理想的ではありません。 ASP.NET が現在の AppDomain を終了し、新しい AppDomain を開始することは比較的簡単なので、 そのような処理が行われると、オブジェクトがすべて失われてしまいます。 このような処理を行わせるには、 たとえば、web.config ファイルを編集して、プロセスを再開するトリガを設定するだけです。

長期間存続するサーバー側オブジェクトを考慮し、管理する場合は、 サーバー上のホスト アプリケーションとして、独自の Windows サービスを作成することをお勧めします。 その結果、AppDomain やプロセスが終了されたり、再開されたりする場合に、 より細かい制御が可能になります。

「Remoting and XML Web Services in Visual Basic .NET」 (英語) で、リモート処理用の Windows サービス ホストを作成する方法を例示しました。 この記事のコードには ObjectServer プロジェクトがあり、 これが Windows サービスです。 このプロジェクトで注意すべき重要な点は、 サーバー側クラスをリモート処理用に構成する方法です。


      RemotingConfiguration.RegisterWellKnownServiceType( _
        GetType(ObjectLibrary.Singleton), _
        "Singleton.rem", _
        WellKnownObjectMode.Singleton)

      RemotingConfiguration.RegisterActivatedServiceType( _
        GetType(ObjectLibrary.StateFul))

まず、クラスを Singleton として構成します。 これは well-known 型ですが、 すべてのクライアントに対して一度に 1 つだけオブジェクトが存在します。 次に、activated 型の状態ありのオブジェクトを構成します。 この場合、クライアントは独自のサーバー側オブジェクトを作成します。

StateFul クラスでは、通常、データやスレッド処理の分離について注意する必要はありません。 必要なことは、常に、そのクラスを MarshalByRefObject から継承することだけです。


Public Class StateFul
  Inherits MarshalByRefObject

  Private mState As String

  Public Sub SetState(ByVal Data As String)

    mState = Data

  End Sub

  Public Function GetState() As String

    Return mState

  End Function

End Class

Singleton クラスはもっと複雑になります。 少なくとも、マルチスレッド処理の問題点を扱う必要があります。 また、あるクライアントのデータを他のクライアントのデータと切り離すことを考える場合は、 何らかの形式の分離を実装することも必要になります。 次の例では、SyncLock ステートメントを使用してスレッド処理の問題点を処理し、 すべてのクライアント間でデータを共有できます。


Public Class Singleton
  Inherits MarshalByRefObject

  Private mState As String

  Public Sub SetState(ByVal Data As String)

    SyncLock Me
      mState = Data
    End SyncLock

  End Sub

  Public Function GetState() As String

    SyncLock Me
      Return mState
    End SyncLock

  End Function

End Class

SyncLock ステートメントを使用して、 一度に 1 つのスレッドだけがそのブロック内で実行できることを保証します。 SyncLock ブロックに入ることを試みる他のすべてのスレッドは、 現在のスレッドがそのブロックを終了するまで、一時停止されます。

これは、スレッド管理の最も単純な例です。 実際のプロジェクトで Singleton クラスを実装することは、 一般的に、これよりもはるかに複雑になります。

Singleton クラスと StateFul クラスは、共に、 クラス ライブラリ プロジェクトの ObjectLibrary に存在することに注意してください。 このようにすることで、 Windows サービスとクライアント プロジェクトの両方でこれらのクラスを参照できます。

クライアントのビルドまたは実行を試みる前に、 installutil.exe コマンド ライン ユーティリティを実行して、 Windows サービスをインストールして、そのサービスが開始されることを確認してください。

クライアントは、おそらく、Windows フォームまたは Web フォームになります。 この例では、Singleton オブジェクトと StateFul オブジェクトを両方呼び出す Windows フォーム プロジェクトです。 同時に複数のクライアント アプリケーションを実行することによって、 シングルトン オブジェクトとアクティブ オブジェクトの状態管理の違いを実験できます。

Singleton クラスと StateFul クラスがリモート サーバーで呼び出されることを認識するように、 クライアントの起動時に、 次のようにリモート処理を構成する必要があります。


    RemotingConfiguration.RegisterWellKnownClientType( _
      GetType(ObjectLibrary.Singleton), _
      "tcp://localhost:15124/ObjectServer/Singleton.rem")

    RemotingConfiguration.RegisterActivatedClientType( _
      GetType(ObjectLibrary.StateFul), _
      "tcp://localhost:15124/ObjectServer")

また、この時点で、次のように StateFul オブジェクトと Singleton オブジェクトのインスタンスを作成します。


    mSingleton = New ObjectLibrary.Singleton()
    mStateFul = New ObjectLibrary.StateFul()

この 2 つのステートメントは同じように見えますが、 意味的に大きな違いがあります。 Singleton オブジェクトを作成するときに行う必要があることは、 共有サーバー側オブジェクトへの参照を作成することだけです。 すべてのクライアントが、サーバー上のまったく同じオブジェクトを参照します。 一方、StateFul オブジェクトを作成するときは、 ここで使用するための新しいサーバー側オブジェクトが作成されます。 他のクライアントはこの新しいオブジェクトにアクセスできません。

クライアント コードの残りの部分では、 オブジェクトの値を設定したり、取得したりするために、 これらのオブジェクトのメソッドを呼び出します。 2 つのクライアント アプリケーションを同時に実行すると、 一方のクライアントで変更した Singleton 値を、もう一方のクライアントで確認できます。 また、一方のクライアントで StateFul 値を変更しても、 もう一方のクライアントにはまったく影響しないことも確認できます。 各クライアントは、独自の StateFul オブジェクトを所持します。

ピア ツー ピアのリモート処理

説明する最後の事例は、ピア ツー ピア (P2P) の事例です。 この場合、各クライアントはサーバーでもあります。 このモデルとこれまでの例の主な違いは、 アプリケーションがクライアント要求を受信待ちするように構成すると同時に、 リモート処理経由でクラスを呼び出すことです。

ほとんどの P2P アプリケーションでは、 特定のピアが同時に複数の他のピアに接続します。 つまり、これまでの例で使用してきたようには、 リモート処理用に同じ形式の静的構成を使用できません。 これまでの例では、クラス (上記の例の StateFul クラスなど) を特定のサーバー名やポートに静的にバインドしました。 P2P 環境では、複数のピアで同じクラスからリモート オブジェクトを作成する必要があるので、 これは機能しません。

これを実現するためには、 リモート処理ではリモート クラスをまったく登録しないことになります。 代わりに、.NET Framework からの特殊なメソッドを使用して、 リモート オブジェクトを作成します。 これは、DCOM を使用するオブジェクトを作成するために、 Visual Basic 6.0 で CreateObject メソッドを使用していたのとやや似ています。

Visual Basic .NET では、これが Activator.GetObject メソッドになり、 このメソッドは 2 つのパラメータを受け取ります。 最初のパラメータは作成するオブジェクトの型で、 2 つ目のパラメータはそのオブジェクトが作成されるリモート処理ホストの URL です。 これは、静的構成を行っていた場合に使用していた URL と同じものです。

ダウンロードしたコードの Peer プロジェクトが P2P モデルを実装します。 このプロジェクトでは、 ピアが他のピアを受信待ちするポートを指定でき、 受信待ちを開始、および停止できます。 受信待ちを開始するときは、リモート処理を次のように構成します。


      ' アプリケーション名を設定します。
      RemotingConfiguration.ApplicationName = "Peer"

      ' チャネルを作成します。
      mChannel = New TcpChannel(CInt(txtPort.Text))
      ChannelServices.RegisterChannel(mChannel)

      ' リスナ クラスを公開します。
      RemotingConfiguration.RegisterWellKnownServiceType( _
        GetType(Listener), _
        "Listener.rem", _
        WellKnownObjectMode.SingleCall)

これは、この記事の前半の Windows サービスでリモート処理を構成するために使用したのと同じプロセスです。 ここでは、アプリケーション名を設定し、 特定のポートで受信待ちをするために TcpChannel を構成して、 Listener クラスを登録しています。 その結果、リモート処理経由で他のピアを利用できるようになります。 このコードで定義されている URL は以下のとおりです。


tcp://localhost:port/Peer/Listener.rem

port をアプリケーションに対してユーザーが指定したポート番号に置き換えます。

一旦、ピアが受信待ちを開始すると、 他のピアがそのピアに接続して、Listener オブジェクトと対話できるようになります。 Listener オブジェクトは、ユーザーが入力したピアの名前を取得できるように、プロパティを公開します。

他のピアの名前を取得するには、 そのピアの名前または IP アドレス、およびそのピアが受信待ちするポートを知っている必要があります。 この情報を使用して、リモート ピアの URL を構築し、Listener オブジェクトを作成できます。


    Dim listener As Listener
    Dim url As New UriBuilder()

    With url
      .Host = txtRemoteServer.Text
      .Port = CInt(txtRemotePort.Text)
      .Scheme = "tcp"
      .Path = "Peer/Listener.rem"
    End With

    listener = Activator.GetObject(GetType(Listener), url.ToString)
    txtRemoteName.Text = listener.Name

ここでは、.NET システム クラス ライブラリからの UriBuilder クラスを使用して、 URL の作成に役立てました。 その後、その URL を使用し、 Activator.GetObject を呼び出すことによって、リモート Listener オブジェクトを作成します。 リモート ピアが見つからない場合は、 この呼び出しは例外を発生するので、 堅牢なアプリケーションにするには、 この呼び出しを Try..Catch ブロックでラップします。

リモート Listener オブジェクトへの参照を所持した後は、 Name プロパティを使用して、リモート ピアの名前を取得できます。

コンピュータで 2 つの Peer アプリケーションを実行することにより、 これを確認できます。 それぞれのピアが異なるポート番号で受信待ちすることを確認するだけです。 サーバー名に localhost を使用して、それぞれの名前を交換できます。

まとめ

リモート処理は、DCOM と Web サービスの交差部分に位置付けられます。 リモート処理は両方のテクノロジの多くの機能を提供し、 現在の分散環境で多くの種類のアプリケーションをビルドするのに非常に適しています。 ビルドできるアプリケーションの種類には、 クライアント/サーバー、サーバー ベース、オートメーション、およびピア ツー ピアなどがあります。

Rockford Lhotka は、『Expert One-on-One Visual Basic .NET Business Objects』の著者で、 他にもさまざまな書籍や記事を執筆しています。 また、世界中の主要なカンファレンスで講演しています。 Rockford は、国内最高の Microsoft Gold Certified Partners の 1 つである Magenic Technologies の Principal Technology Evangelist です。