MS的にはWCFにすべきだったのかもしれないが、同一コンピュータ上でのプロセス間通信としてIPCを利用した方式を使ってみた。
以下のURLを読んでおくといい。コード例は特に最後のURLが短くて分かりやすい。
- .NET Remoting 双方向通信
http://axion.sakura.ne.jp/TwoWayComm_dotNetRemoting.html - Two-way Remoting with Callbacks and Events, Explained - CodeProject
http://www.codeproject.com/Articles/13847/Two-way-Remoting-with-Callbacks-and-Events-Explain - A Simple Callback-to-Client Scenario Using .NET Remoting
http://social.msdn.microsoft.com/Forums/en-US/bde99017-ba72-498d-90b3-e21fd145ff2b/a-simple-callbacktoclient-scenario-using-net-remoting?forum=netfxremoting- 下のほうのコメントにあるようにportNameを指定するように変更する必要があるのは注意。
概要
IPCを利用してクライアントとサーバの2プロセス間で
- クライアントからサーバ側のメソッドを呼び出す。
- クライアントからサーバ側にコールバック関数を登録して、サーバ側から→クライアントに呼び返す。
プロジェクトを3つ作成する。
- サーバとクライアントで共有するクラスなどを定義するDLLのプロジェクト
- サーバ側実行モジュール
- クライアント側実行モジュール
DLLのプロジェクト
サーバ⇔クライアントで互いに参照するものはMarshalByRefObject、受け渡すデータはSerializableにする。
- サーバ→クライアント方向のコールバック関数で受け渡すデータのクラスを定義する。Serializableにする。
- コールバック関数のdelegateを定義する。↑で作ったSerializableのクラスをパラメーターで受け取るようにする。
- コールバック関数のdelegateを格納するクラスを定義する。MarshalByRefObjectにする。
- このクラスをクライアント側で生成して、サーバに参照を渡す形になる。
- 特に[OneWay]は不要。IPCは下回りは名前付きパイプだから?
- サーバ側で公開するインターフェースを定義する。MarshalByRefObjectにする。
- このクラスはサーバ側で生成して、クライアントから参照する形になる。
using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Security.Permissions; using System.Web.Script.Serialization; // コールバック関数 public delegate void DelegateOnEvent(EventArg eventArg); /// <summary> /// サーバ→クライアントへ渡すパラメタ /// </summary> [Serializable] public class EventArg : ISerializable { public Dictionary<string, string> params; public EventArg(Dictionary<string, string> params) { this.params = params; } /// <summary> /// 逆シリアル化 /// </summary> /// <param name="info"></param> /// <param name="context"></param> [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] protected EventArg(SerializationInfo info, StreamingContext context) { // 自前でやるときは実装する。以下は一例。 var ser = new JavaScriptSerializer(); var json = info.GetString("params"); this.configuration = ser.Deserialize<Dictionary<string, string>>(json); } /// <summary> /// シリアル化 /// </summary> /// <param name="info"></param> /// <param name="context"></param> [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] public void GetObjectData(SerializationInfo info, StreamingContext context) { // 自前でやるときは実装する。以下は一例。 var ser = new JavaScriptSerializer(); var json = ser.Serialize(params); info.AddValue("params", json); } } /// <summary> /// クライアントがコールバック関数を登録する際に使用する。 /// </summary> public class EventCallbackSink : MarshalByRefObject { public event DelegateOnEvent OnEvent; public EventCallbackSink() { } public void CallbackToClient(EventArg evt) { if (OnEvent != null) { OnEvent(evt); } } } /// <summary> /// サーバが外部プロセスに対して公開するインタフェース /// </summary> public class IpcServerApi : MarshalByRefObject { private static List<DelegateOnEvent> eventListeners = new List<DelegateOnEvent>(); public void AddEventListener(DelegateOnEvent listener) { eventListeners.Add(listener); } public static void RaiseEventToClient(Dictionary<string, string> params) { EventArg evt = new EventArg(params); foreach (var listener in eventListeners) { listener(evt); } } /// <summary> /// このインターフェースの有効期間を無制限にする。 /// </summary> /// <returns></returns> public override object InitializeLifetimeService() { return null; } }
サーバ側
- IpcChannelを生成する→RegisterChannel()
- 双方向の場合IpcServerChannelではなく、IpcChannel。
- BinaryServerFormatterSinkProvider の BinFormatter.TypeFilterLevel = TypeFilterLevel.Fullを指定する。
- 指定しないと「セキュリティ レベルでは逆シリアル化することが許可されていません」エラーとなる。
- RemotingConfiguration.RegisterWellKnownServiceType() あるいは RemotingServices.Marshal() で公開する。
- c# - In .NET remoting what is the difference between RemotingConfiguration.RegisterWellKnownServiceType and RemotingServices.Marshal? - Stack Overflow
http://stackoverflow.com/questions/493455/in-net-remoting-what-is-the-difference-between-remotingconfiguration-registerwe
- c# - In .NET remoting what is the difference between RemotingConfiguration.RegisterWellKnownServiceType and RemotingServices.Marshal? - Stack Overflow
using System.Collections.Generic; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc; // IPCチャンネルの生成 string portName = "aaaaaaaa"; var channelProperties = new Dictionary<string, string>(); channelProperties.Add("portName", portName); var channel = new IpcChannel(channelProperties, null, new BinaryServerFormatterSinkProvider { TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full, }); ChannelServices.RegisterChannel(channel, true); var server = new IpcServerApi(); RemotingServices.Marshal(server, "bbbbbbbb", typeof(IpcServerApi)); // ...中略... // イベント送信時 var params = new Dictionary<string, string>(); server.RaiseEventToClient(params);
クライアント側
- IpcChannelを生成する→RegisterChannel()
- 双方向の場合IpcClientChannelではない。
- BinaryServerFormatterSinkProvider の BinFormatter.TypeFilterLevel = TypeFilterLevel.Fullを指定する。
- Activator.GetObject()でサーバ側のオブジェクトを取得する。
- コールバック関数の登録
- コールバック関数のdelegateを格納するクラスのオブジェクトを生成する。それにdelegateを設定する。
- ↑のクラスのdelegateをもう一回delegateでラップしてサーバに渡す。(ややこしいけど言い方合ってる?)
- 方法 : リモート オブジェクトのメソッドを非同期で呼び出す
http://msdn.microsoft.com/ja-jp/library/vstudio/3k559b9a(v=vs.100).aspx
using System; using System.Collections.Generic; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc; // IPCチャンネルの生成 string portName = "cccccccc"; var channelProperties = new Dictionary<string, string>(); channelProperties.Add("portName", portName); var channel = new IpcChannel(channelProperties, null, new BinaryServerFormatterSinkProvider { TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full, }); ChannelServices.RegisterChannel(channel, true); // リモートオブジェクトを取得 var server = Activator.GetObject(typeof(IpcServerApi), "ipc://aaaaaaaa/bbbbbbbb") as IpcServerApi; // リスナの登録 var callbackSink = new EventCallbackSink(); callbackSink.OnEvent += new DelegateOnEvent(EventListener); var callbackDelegate = new DelegateOnEvent(callbackSink.CallbackToClient); server.AddEventListener(callbackDelegate); public void EventListener(EventArg evt) { }
問題点
- クライアント側がプロセスを終了して、また起動して、リスナーを登録しなおした時、何かおかしい。
- EventCallbackSink()を生成したクライアント プロセスが終了しても、サーバ側のMarshalByRefObjectが残っていておかしい?
- InitializeLifetimeService()をもう少しキチンと実装すべきなのか?