2010年1月18日月曜日

[VB.NET]リモートオブジェクトでインタフェースと実装の分離手法の一案

サーバー/クライアント間通信を実装するのはいつも面倒です。
前に作ったコードをベースに新たにサーバーを起こしてみたり。

.NETになって「.NETリモート処理」なるものが実装されました。
結構お手軽なのですが、ここで問題になるのが「クライアントにもリモート処理可能オブジェクト(DLL)」が必要なこと。

これは、コンパイル時に型を参照する為ばかりでなく、実行時にも必要です。
せっかくサーバ側とクライアント側を分離したはずなのに、リモート処理可能オブジェクトと共にサーバ側だけでしか必要の無いDLL等が必要なことを意味します。
また、クライアント側に実装の詳細が見えてしまいかねません。

これの解決策は、MSDNのNET Framework開発者ガイドに記載がありますが、
「別のライブラリでインターフェイスを宣言し、そのライブラリをクライアントと共に配置する方法。」
です。
でも、こんな方法もあるということで。
(一応、VB.NET 2003~2008まで使えています)

1)サーバーソリューションを作り、クラスライブラリのプロジェクトを作ります。
クラスライブラリプロジェクト内に下記のようにインタフェースだけを定義したクラスを作ります。
コンパイルすると、インタフェースクラスライブラリのDLLが出来上がります。

Public MustInherit Class ServerObject
    Inherits MarshalByRefObject

    Public MustOverride Function Func1(ByVal param) as Integer
End Class
2)サーバソリューション内にそのライブラリを参照したサーバープロジェクトを作ります。
サーバープロジェクト内に、インタフェースから派生したクラスとオブジェクトを登録するモジュールを作ります。
これらは同一実行モジュール内におくことが出来ます。
ポイントは、RemotingConfiguration.RegisterWellKnownServiceType()で登録するオブジェクトは、インタフェースクラスでなく、派生クラスの物を登録することです。

Imports System
Imports System.Threading
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http

Public Class Server
    Inherits ServerObject

    Public Overrides Function Func1(ByVal param) as Integer
        Return param * 10    ' とリあえず簡単なもの^^;
    End Function
End Class


Module mdlMain

    Sub Main()
        Dim channel As HttpChannel = New HttpChannel(30000)
        ChannelServices.RegisterChannel(channel)

        ' サービスタイプを登録
        RemotingConfiguration.RegisterWellKnownServiceType(GetType(Server), _
            "Server", WellKnownObjectMode.SingleCall)

        ' ウィンドウが閉じられるまで待機
        Dim sync As Object = New Object
        SyncLock sync
            Monitor.Wait(sync)
        End SyncLock
    End Sub
End Module
3)クライアントは、別のソリューションを作ります。
先にサーバーで作成したインタフェースを定義したライブラリプロジェクトを追加し、(既存のプロジェクトの追加)
新たにクライアント本体のプロジェクトを作ります。
そして、以下のようにクライアントプロジェクトを記述します。
ポイントは、Activator.GetObject()でインタフェースのオブジェクトを取得するように記述することです。

Module mdlMain

    Sub Main()
        ' HTTPチャンネルを登録
        Dim chan as New HttpChannel

        ChannelServices.RegisterChannel(chan)

        ' GetTypeの引数 = "インタフェースDLLの名前空間.クラス名, インタフェースDLLのアセンブリ名" ← 混乱しやすいので注意!!!
        Dim obj as Object = Activator.GetObject(Type.GetType("SvrObj.ServerObject, SvrObj"), _
            "http://localhost:30000/Server")

        If Not (obj Is Nothing) Then
            Dim svr As ServerObject = CType(obj, ServerObject)
            Dim i as integer

            i = svr.Func1(10)
            Console.WriteLine("Server answer = " + i.ToString())
        Else
            Console.WriteLine("Object nothing.")
        End If
    End Sub
End Module
これで、クライアント側には、クライアントモジュールEXEとインタフェースDLLのみ配置すれば実行することができます。
欠点は、全てのインタフェースAPIをMustOverrideで定義しなければいけないことでしょうか。

【注意事項】ここに掲載されている内容について引用流用は自由ですが、内容やサンプルに基づくいかなる結果に関して一切の責任を負いません。自己の責任の上でご活用ください。