トップ回答者
アプリケーション間連携

質問
-
プロセス間通信、ipcChannel、appDomain間通信・・・等のキーワードであたっていましたが、
基本を理解していないので質問させていただきます。
現在vb.NETにおいてアプリAを開発しています。
実現したいことは、このアプリAと他社製品アプリB間で、相互にデータの受け渡しを行うということです。
アプリBの開発担当者と、どのような方法で連携するかを検討中です。
方法を検索していますが、全く異なるドメイン間で実現出来るのかが分かりません。
サンプルを見ていると、ひとつのソリューションにクラスライブラリ用プロジェクト、サーバ側用プロジェクト、
クライアント側用プロジェクトの3つを作成し、サーバとクライアント側でライブラリを参照している例が
数多くあります。
開発会社が違いますので、上記サンプルで見られるようなことはできません。
今回の場合、「プロセス間通信」というものは該当しないのではないかと思うのですが、間違って
おりますでしょうか。
appDomain間・・・が今回のケースでしょうか。
appDomain間連携ということであれば、どのように実現できますでしょうか。
漠然としていて申し訳ございませんが、よろしくお願いします。
参考
http://blog.livedoor.jp/akf0/archives/51524577.html
http://msdn.microsoft.com/ja-jp/events/dd252959.aspx
http://msdn.microsoft.com/ja-jp/library/cc825640.aspx
http://d.hatena.ne.jp/coma2n/20070918/1190091655
回答
-
今回されたいことは「異なるプロセス間での通信」であるため、「プロセス間通信」の様々な方法が使用できると思います。
appDomain 間と言うと、たぶん同じプロセス内での話になると思います。
サンプルの件ですが、1つのソリューションにした方がサンプルとしては解りやすいためにそうなっているものが多いと思いますが、ソリューション内のプロジェクト単位で開発会社が異なっていても良く、同じソリューションである必要はありません。
同じライブラリを参照している件については、それぞれの会社で同じクラス(通信内容を示すもの)を定義されるとよいはずです(通信内容は取り決めが必要になりますので、同じ定義を行う点には問題は発生しないと思います)。
まずはそのサンプルの内容を十分に把握されてはいかがでしょうか。
プロセス間通信については、たしかごく最近、Visual Studio フォーラムのどこかにスレッドが上がっていたと思いますし、検索などによって情報はたくさん得られるとも思います。
(追記:というか、すでに TwSoft さんが書かれたリンク先でも十分な気もしました)
ありました↓
EXE間通信
http://social.msdn.microsoft.com/Forums/ja-JP/vbgeneralja/thread/af02128a-25ef-4891-a553-3bf493a3e40b
上記のスレッドにも登場しますが、双方が .NET なのでしたら .NET Remoting を使うと、驚くほど簡単に実現できます。
たとえば IpcServerChannel や TcpServerChannel クラスの MSDN のサンプルなども参考になると思います。 -
異なるプロセス間にて、まるでインスタンスを共有しているかのように扱えることがポイントですね。
ただし役割として、サーバー側/クライアント側、という意識は必要になります。
※以下は VB.NET にしたものですが、リンク先のそのままではありません。'●双方で参照するライブラリ Public Class MyRemoteObject Inherits MarshalByRefObject Public Delegate Sub MyEventHandler(ByVal str As String) Public Event MyEvent As MyEventHandler Public Sub RaiseMyEvent(ByVal str As String) RaiseEvent MyEvent(str) End Sub Private _myFlag As Boolean Public Property MyFlag() As Boolean Get Return _myFlag End Get Set(ByVal value As Boolean) If _myFlag <> value Then _myFlag = value RaiseEvent MyFlagChanged(Me, EventArgs.Empty) End If End Set End Property Public Event MyFlagChanged(ByVal sender As Object, ByVal e As EventArgs) End Class '●サーバー側 '参照設定の追加:System.Runtime.Remoting Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Ipc '参照設定の追加:通信先との共通ライブラリ Imports ClassLibrary1 Module ModuleServer Sub Main() Dim servChannel As New IpcServerChannel("remote") ChannelServices.RegisterChannel(servChannel, True) Console.WriteLine("Listening: {0}", servChannel.GetChannelUri()) Dim myRemoteTest As New MyRemoteObject() AddHandler myRemoteTest.MyEvent, AddressOf myRemoteTest_MyEvent AddHandler myRemoteTest.MyFlagChanged, AddressOf myRemoteTest_MyFlagChanged RemotingServices.Marshal(myRemoteTest, "test", GetType(MyRemoteObject)) Console.ReadLine() End Sub Sub myRemoteTest_MyEvent(ByVal str As String) Console.WriteLine(str) End Sub Sub myRemoteTest_MyFlagChanged(ByVal sender As Object, ByVal e As EventArgs) Dim myRemoteTest = DirectCast(sender, MyRemoteObject) Console.WriteLine("MyFlag プロパティの値が '{0}' に変化しました。", myRemoteTest.MyFlag) End Sub End Module '●クライアント側 Imports System.Runtime.Remoting.Channels.Ipc Imports System.Runtime.Remoting.Channels Imports ClassLibrary1 Module ModuleClient Sub Main() Dim clientChannel As New IpcClientChannel() ChannelServices.RegisterChannel(clientChannel, True) Dim myRemoteTest = _ DirectCast( _ Activator.GetObject(GetType(MyRemoteObject), "ipc://remote/test"), _ MyRemoteObject) myRemoteTest.RaiseMyEvent("クライアントからサーバーへのメッセージです。") myRemoteTest.MyFlag = True End Sub End Module
- 回答としてマーク 山本春海 2010年5月21日 1:49
-
自己レスです。
vb.net版のプロセス間通信がとりあえずできました。
今回はソリューションを3つ別々に作成しました。
・リモートで呼び出されるクラス用ソリューション
・サーバ側フォームアプリケーション用ソリューション
・クライアント側フォームアプリケーション用ソリューション-------------------------------------------------------------------------------------
クラスのソースは以下の通りです。(アセンブリ名:RemoteObject)Public Class RemoteMessage
Inherits MarshalByRefObjectPublic Delegate Sub CallHandler(ByVal Str As String)
Public Event eventCall As CallHandlerPublic Sub hoge(ByVal str As String)
RaiseEvent eventCall(str)
End SubEnd Class
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
サーバ側では、上記ライブラリとSystem.Runtime.Remotingを参照設定しました。
サーバ側のForm1のソースは以下のの通りです。Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Ipc
Imports RemoteObjectPublic Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim svChannel As IpcServerChannel = New IpcServerChannel("remote")ChannelServices.RegisterChannel(svChannel, True)
Dim msg As RemoteMessage = New RemoteMessage()
AddHandler msg.eventCall, New RemoteMessage.CallHandler(AddressOf msg_eventCall)RemotingServices.Marshal(msg, "message", GetType(RemoteMessage))
End SubPrivate Shared Sub msg_eventCall(ByVal str As String)
MessageBox.Show(str)
End Sub
End Class
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
クライアント側もサーバ側と同じ参照設定をします。
クライアント側のForm1のコードは以下の通りです。Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Ipc
Imports RemoteObjectPublic Class Form1
Dim clientChannel As IpcClientChannel
Dim msg As RemoteMessagePublic Sub New()
InitializeComponent()clientChannel = New IpcClientChannel()
ChannelServices.RegisterChannel(clientChannel, True)
msg = DirectCast(Activator.GetObject(GetType(RemoteMessage), "ipc://remote/message"), RemoteMessage)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
msg.hoge(Now().ToString)
End Sub
End Class
-------------------------------------------------------------------------------------とりあえず上記の方法により、クライントのイベントで他のプロセスのイベントが発生し、そこに
データを受け渡すことができました。ありがとうございました。
- 回答としてマーク 山本春海 2010年5月21日 1:49
すべての返信
-
今回されたいことは「異なるプロセス間での通信」であるため、「プロセス間通信」の様々な方法が使用できると思います。
appDomain 間と言うと、たぶん同じプロセス内での話になると思います。
サンプルの件ですが、1つのソリューションにした方がサンプルとしては解りやすいためにそうなっているものが多いと思いますが、ソリューション内のプロジェクト単位で開発会社が異なっていても良く、同じソリューションである必要はありません。
同じライブラリを参照している件については、それぞれの会社で同じクラス(通信内容を示すもの)を定義されるとよいはずです(通信内容は取り決めが必要になりますので、同じ定義を行う点には問題は発生しないと思います)。
まずはそのサンプルの内容を十分に把握されてはいかがでしょうか。
プロセス間通信については、たしかごく最近、Visual Studio フォーラムのどこかにスレッドが上がっていたと思いますし、検索などによって情報はたくさん得られるとも思います。
(追記:というか、すでに TwSoft さんが書かれたリンク先でも十分な気もしました)
ありました↓
EXE間通信
http://social.msdn.microsoft.com/Forums/ja-JP/vbgeneralja/thread/af02128a-25ef-4891-a553-3bf493a3e40b
上記のスレッドにも登場しますが、双方が .NET なのでしたら .NET Remoting を使うと、驚くほど簡単に実現できます。
たとえば IpcServerChannel や TcpServerChannel クラスの MSDN のサンプルなども参考になると思います。 -
TH01様
ありがとうございます。
「異なるプロセス間通信」。理解できました。
その後、参照していたページを参考に、それぞれソリューションを作成しテストしてみました。
その結果、とりあえず「プロセス間通信」は実現できました。
が、実際の動きを考えますと、クライアント側のイベント発生で、サーバ側のイベントを起動し
何らかの動作をさせることが最終目的です。
そこで下記のサンプルに辿り着きました。
http://d.hatena.ne.jp/tetsuarossa/20070325/p1
大変お恥ずかしいのですが、C#は経験がなく、このコードのライブラリオブジェクト部分とサーバ側の
コードをvb.netに置き換えた場合のコードで現在はまっています。
-
異なるプロセス間にて、まるでインスタンスを共有しているかのように扱えることがポイントですね。
ただし役割として、サーバー側/クライアント側、という意識は必要になります。
※以下は VB.NET にしたものですが、リンク先のそのままではありません。'●双方で参照するライブラリ Public Class MyRemoteObject Inherits MarshalByRefObject Public Delegate Sub MyEventHandler(ByVal str As String) Public Event MyEvent As MyEventHandler Public Sub RaiseMyEvent(ByVal str As String) RaiseEvent MyEvent(str) End Sub Private _myFlag As Boolean Public Property MyFlag() As Boolean Get Return _myFlag End Get Set(ByVal value As Boolean) If _myFlag <> value Then _myFlag = value RaiseEvent MyFlagChanged(Me, EventArgs.Empty) End If End Set End Property Public Event MyFlagChanged(ByVal sender As Object, ByVal e As EventArgs) End Class '●サーバー側 '参照設定の追加:System.Runtime.Remoting Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Ipc '参照設定の追加:通信先との共通ライブラリ Imports ClassLibrary1 Module ModuleServer Sub Main() Dim servChannel As New IpcServerChannel("remote") ChannelServices.RegisterChannel(servChannel, True) Console.WriteLine("Listening: {0}", servChannel.GetChannelUri()) Dim myRemoteTest As New MyRemoteObject() AddHandler myRemoteTest.MyEvent, AddressOf myRemoteTest_MyEvent AddHandler myRemoteTest.MyFlagChanged, AddressOf myRemoteTest_MyFlagChanged RemotingServices.Marshal(myRemoteTest, "test", GetType(MyRemoteObject)) Console.ReadLine() End Sub Sub myRemoteTest_MyEvent(ByVal str As String) Console.WriteLine(str) End Sub Sub myRemoteTest_MyFlagChanged(ByVal sender As Object, ByVal e As EventArgs) Dim myRemoteTest = DirectCast(sender, MyRemoteObject) Console.WriteLine("MyFlag プロパティの値が '{0}' に変化しました。", myRemoteTest.MyFlag) End Sub End Module '●クライアント側 Imports System.Runtime.Remoting.Channels.Ipc Imports System.Runtime.Remoting.Channels Imports ClassLibrary1 Module ModuleClient Sub Main() Dim clientChannel As New IpcClientChannel() ChannelServices.RegisterChannel(clientChannel, True) Dim myRemoteTest = _ DirectCast( _ Activator.GetObject(GetType(MyRemoteObject), "ipc://remote/test"), _ MyRemoteObject) myRemoteTest.RaiseMyEvent("クライアントからサーバーへのメッセージです。") myRemoteTest.MyFlag = True End Sub End Module
- 回答としてマーク 山本春海 2010年5月21日 1:49
-
自己レスです。
vb.net版のプロセス間通信がとりあえずできました。
今回はソリューションを3つ別々に作成しました。
・リモートで呼び出されるクラス用ソリューション
・サーバ側フォームアプリケーション用ソリューション
・クライアント側フォームアプリケーション用ソリューション-------------------------------------------------------------------------------------
クラスのソースは以下の通りです。(アセンブリ名:RemoteObject)Public Class RemoteMessage
Inherits MarshalByRefObjectPublic Delegate Sub CallHandler(ByVal Str As String)
Public Event eventCall As CallHandlerPublic Sub hoge(ByVal str As String)
RaiseEvent eventCall(str)
End SubEnd Class
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
サーバ側では、上記ライブラリとSystem.Runtime.Remotingを参照設定しました。
サーバ側のForm1のソースは以下のの通りです。Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Ipc
Imports RemoteObjectPublic Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim svChannel As IpcServerChannel = New IpcServerChannel("remote")ChannelServices.RegisterChannel(svChannel, True)
Dim msg As RemoteMessage = New RemoteMessage()
AddHandler msg.eventCall, New RemoteMessage.CallHandler(AddressOf msg_eventCall)RemotingServices.Marshal(msg, "message", GetType(RemoteMessage))
End SubPrivate Shared Sub msg_eventCall(ByVal str As String)
MessageBox.Show(str)
End Sub
End Class
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
クライアント側もサーバ側と同じ参照設定をします。
クライアント側のForm1のコードは以下の通りです。Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Ipc
Imports RemoteObjectPublic Class Form1
Dim clientChannel As IpcClientChannel
Dim msg As RemoteMessagePublic Sub New()
InitializeComponent()clientChannel = New IpcClientChannel()
ChannelServices.RegisterChannel(clientChannel, True)
msg = DirectCast(Activator.GetObject(GetType(RemoteMessage), "ipc://remote/message"), RemoteMessage)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
msg.hoge(Now().ToString)
End Sub
End Class
-------------------------------------------------------------------------------------とりあえず上記の方法により、クライントのイベントで他のプロセスのイベントが発生し、そこに
データを受け渡すことができました。ありがとうございました。
- 回答としてマーク 山本春海 2010年5月21日 1:49
-
目的は達成できましたが、新たな問題が発生しました。
Dim svChannel As IpcServerChannel = New IpcServerChannel("remote")
上記箇所で「IPCポートを作成できません。アクセスが拒否されました」というエラーになります。
このエラーは、以下の手順で発生します。
サーバ側Aを起動 -> クライアント側Bを起動 -> Bから何らかのアクション -> 成功
-> Aを終了 -> Aを再起動
このオペレーションで発生します。
http://www31.atwiki.jp/memo77/pages/25.html
解決法が上記サイトにあったため、クライアント側のコードに
msg.hoge(Now().ToString)
msg = nothingを追加しました。
しかしエラーは回避できません。
この情報が誤りだ・・・という情報もあり、回避策が見つかりません。
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=46108&forum=7
アドバイスをお願いいたします。
-
TH01様
いつもありがとうございます。
ご返事できなくて申し訳ございませんでした。
MainメソッドのApplication.Runの後にStopListening(Nothing)を記述してみましたが
エラーは回避されませんでした。
仕方がないので、今回はエラー処理の中で「プロセスが残っているから、もう少したってから
起動してください」という感じのメッセージを出すようにしました。
なんだかカッコ悪い気がしてなりませんが、どのようにしても解決しないため、今回はここで
諦めます。
端末に依存するかどうかは調べていないため、環境を変えるなどして後日確認してみます。
TH01様、ありがとうございました。
-
こんなスレッド見つけました。
そこでは質問者さんが StopListening しても駄目と書かれていました。。。お手数おかけしました。
回答マークの付いている返信がとても有効そうです。
IPCChannel explicit clean-up?
http://social.msdn.microsoft.com/Forums/en/netfxremoting/thread/d154e4a9-3e31-41a5-944c-db867ca77e9e -
TH01様
ありがとうございます。
エラーを回避することができ、ただしく動作することを確認しましたが、こちらが立てば
あちらが立たず・・・のような状況に陥りました。
サーバ側起動 -> クライアント側起動 -> クライアント側でイベント発生 -> サーバ側が応答
-> サーバ側終了 -> サーバ側再起動(エラーなく起動) -> クライアント側でイベント発生
このタイミングで「IPCポートに書き込むことができません。パイプを閉じています」という
エラーが発生します。
Dim svChannel As IpcServerChannel
Dim channelProperties As New Hashtable()channelProperties.Add("portName", "remote")
channelProperties.Add("exclusiveAddressUse", False)svChannel = New IpcServerChannel(channelProperties, Nothing)
svChannel.IsSecured = False
ChannelServices.RegisterChannel(svChannel, False)Lifetime.LifetimeServices.LeaseTime = TimeSpan.Zero
Lifetime.LifetimeServices.RenewOnCallTime = TimeSpan.Zero引き続きチャレンジしてみます。
-
TH01様
度々ご調査いただき、ありがとうございます。
クライアント側のボタンクリック時に、下記のメソッドを実行しています。
Public Sub ServerCall()
Dim objRemote = _
DirectCast( _
Activator.GetObject(GetType(RemoteMessage), "ipc://remote/library"), _
RemoteMessage)Try
objRemote.RaiseServerSideEvent() '<-ここでエラー
Catch ex As RemotingException
MessageBox.Show(ex.Message)Finally
objRemote = Nothing
End Try
End Sub
サーバ側の終了時にStopListening(Nothing)を記述しておりますが、状況は改善されません。
タイミングといえばタイミングですが、エラー時にメッセージボックスを閉じ、直ちに再度イベントを
発生させると、正しく動作します。
再起動する必要はありませんでした。
うまく説明できませんが、サーバ側再起動後、必ず初回のイベントでは「パイプを閉じています」の
エラーが発生し、2回目以降のイベントは正しく処理されているような状況です(再起動必要なし)。
ですので、最悪はエラー時に「前回の情報が残っています。アプリAとアプリBを再起動してください」
といったようなアラートを出そうかなと考えております。