- PR -

.NET Remotingでのカスタムクラス利用

1
投稿者投稿内容
HAN
会議室デビュー日: 2007/05/24
投稿数: 5
投稿日時: 2007-05-24 17:50
WebサービスとWindowsサービスとの間でRemotingによるイベント通知、データ取得を行っています。

これまではRemotingで使用していたクラスのメンバ変数は全てプリミティブ型でしたので、問題なくイベントが通知されていたのですが、内部で複数のクラス階層を持ったカスタムクラスを実装したところ、Windowsサービス側で登録しているイベントハンドラをWebサービス側から認識できない現象が起きています。

カスタムクラスを利用するためには、何をどのように変更すれば良いのでしょうか?

Remoting環境の概要

◎Windowsサービス側で行っている主な処理
・Marshal実行
ObjRef oref = RemotingServices.Marshal(RemoteClass, "RemoteClass.rem");

・イベントハンドラ登録
c_remoteClass.CalledClient += new RemoteClass.CalledClientHandler(RemoteCalled);

◎Webサービス(IIS)側で行っている主な処理
・チャンネル設定
HttpChannel channel = new HttpChannel(0);
ChannelServices.RegisterChannel(channel, false);

・送信パス設定
RemotingConfiguration.RegisterWellKnownClientType(
typeof(RemoteClass), "http://localhost:8989/RemoteClass.rem");

◎カスタムクラスの例
public class RemoteClass : MarshalByRefObject
{
// デリゲータ
public delegate void CalledClientHandler(enBsProcessType bsProcessType);
// イベント
public event CalledClientHandler CalledClient;

private string c_testString;

public string TestString
{
get { return c_testString; }
set { c_testString = value; }
}

/// <summary>
/// イベントを通知してサービスを呼び出す
/// </summary>
/// <param name="enProcessType ">処理タイプ</param>
public void CallService(enProcessType i_bsProcessType)
{
if (CalledClient != null) ------------------@
{
// Webサービスからのイベントを通知
CalledClient(i_bsProcessType);
}
}
}

上記@の部分で他のプリミティブ型のクラスであれば、CalledClient内にイベントハンドラの参照が得られますが、カスタムクラスの場合、CalledClientはNull状態になってしまいます。

どうかよろしくお願い致します。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-05-24 21:26
こんにちは。

「プリミティブ型」という言葉はInt32やDouble、Byteなどのことを指しているという認識でいいですよね。
確認ですが、HANさんのおっしゃる「プリミティブ型」にStringは含まれますか?.NETではあんまり聞かない言葉なので、どんな意味で使っているのか聞いておいた方がいいと思って。値型という意味で使っているんでしょうか。

引用:

HANさんの書き込み (2007-05-24 17:50) より:
これまではRemotingで使用していたクラスのメンバ変数は全てプリミティブ型でしたので、問題なくイベントが通知されていたのですが、内部で複数のクラス階層を持ったカスタムクラスを実装したところ、Windowsサービス側で登録しているイベントハンドラをWebサービス側から認識できない現象が起きています。


Remotingで使用していたクラスというのはRemoteClassのことですよね。

 public class RemoteClass : MarshalByRefObject
 {
  public delegate void CalledClientHandler(enBsProcessType bsProcessType);
  public event CalledClientHandler CalledClient;

  public int a;

  public void CallService(enProcessType i_bsProcessType)
  {
   if (CalledClient != null)
   {
    // Webサービスからのイベントを通知
    CalledClient(i_bsProcessType);
   }
  }
 }

これだと大丈夫だけど、

 public class RemoteClass : MarshalByRefObject
 {
  public delegate void CalledClientHandler(enBsProcessType bsProcessType);
  public event CalledClientHandler CalledClient;

  public int a;
  public ArrayList b;

  public void CallService(enProcessType i_bsProcessType)
  {
   if (CalledClient != null)
   {
    // Webサービスからのイベントを通知
    CalledClient(i_bsProcessType);
   }
  }
 }

メンバ変数にプリミティブでない変数を追加すると、たとえ変数bを使わなくてもCalledClientにハンドラを追加できないということでしょうか。

引用:

HANさんの書き込み (2007-05-24 17:50) より:
Windowsサービス側で登録しているイベントハンドラをWebサービス側から認識できない現象が起きています。


Windowsサービス上のオブジェクトをWebサービス側からリモーティングで扱っているんですよね?
RemoteClass.CallService()はWindowsサービス上で動いているので、Webサービス側で認識するとかしないとかいう話ではないと思います。
WebサービスでRemoteClass.CallService()を呼んでも、サーバー側で実行されるCallService()中でCalledClientイベントに追加したはずのイベントハンドラが追加されていないということですか?
イベントハンドラを追加したオブジェクトと、Webサービスから要求があって生成されたオブジェクトは同じですか?
生成されるオブジェクトのモード(WellKnownObjectMode列挙型で表されるモード)はSingletonになっていますか?

引用:

HANさんの書き込み (2007-05-24 17:50) より:
上記(1)の部分で他のプリミティブ型のクラスであれば、CalledClient内にイベントハンドラの参照が得られますが


「他のプリミティブ型のクラスであれば」というのはどれがプリミティブ型ではないと言っていますか?
イベントの型(デリゲート型)はもちろんプリミティブ型ではありませんよね。
リモーティングで使用する型(RemoteClass)もMarshalByRefObjectを継承するためプリミティブにはなりえません。

あと、ソースの中によく分からない型や変数がいきなり出てきますので、現象を再現できる最低限のプログラムを作ってソースを見せてください。
HAN
会議室デビュー日: 2007/05/24
投稿数: 5
投稿日時: 2007-05-25 14:19
一郎さん、
ご回答ありがとうございます^^

>「プリミティブ型」という言葉はInt32やDouble、Byteなどのことを指しているという認識でいいですよね。
>確認ですが、HANさんのおっしゃる「プリミティブ型」にStringは含まれますか?

MSDNによると、プリミティブ型とはSystem名前空間に含まれる型のことのようです。
String型も含まれます。

以下、長くなりますが、サンプルのコードを記載しますね。

◎Remotingで使用するClass(イベント通知不可のClass)

using System;
using System.Collections.Generic;
using System.Text;

namespace RemoteTest
{
public enum enProcessType { ParentClass, RemoteTestClass }

// 子Class
public class ChildClass
{
private InnerClass c_inClass;

public InnerClass InClass
{
get{ return c_inClass; }
set{ c_inClass = value; }
}

// 孫Class
public class InnerClass
{
private string c_xString;

public string XString
{
get{ return c_xString; }
set{ c_xString = value; }
}
}
}

// 親Class
public class ParentClass : MarshalByRefObject
{
   public delegate void CalledClientHandler(enProcessType processType);
   public event CalledClientHandler CalledClient;

private ChildClass c_child;
private string c_response;

public ChildClass Child
{
get{ return c_child; }
set{ c_child = value; }
}

public string Response
{
get { return c_response; }
set { c_response = value; }
}

   public void CallService(enProcessType i_bsProcessType)
   {
    if (CalledClient != null)
    {
     // Webサービスからのイベントを通知
     CalledClient(i_bsProcessType);
    }
   }
}

public class RemoteTestClass : MarshalByRefObject
{
public delegate void CalledClientHandler(enProcessType processType);
   public event CalledClientHandler CalledClient;

private string c_testString;
private string c_response;

public string TestString
{
get { return c_testString; }
set { c_testString = value; }
}

public string Response
{
get { return c_response; }
set { c_response = value; }
}

public void CallService(enProcessType i_bsProcessType)
{
if (CalledClient != null)
{
// Webサービスからのイベントを通知
CalledClient(i_bsProcessType);
}
}
}
}

◎Webサービスのコード

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using RemoteTest;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
HttpChannel channel;

// リモートクラス定義
ParentClass c_parentClass;
RemoteTestClass c_testClass;

/// <summary>
/// コンストラクタ
/// </summary>
public Service()
{

//デザインされたコンポーネントを使用する場合、次の行をコメントを解除してください
InitializeComponent();
}

/// <summary>
/// 初期化処理
/// </summary>
private void InitializeComponent()
{
if (ChannelServices.RegisteredChannels.Length == 0)
{
// チャンネル作成を作成し送信パスの登録を行う
channel = new HttpChannel(0);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownClientType(
typeof(ParentClass), "http://localhost:8989/ParentClass.rem");
RemotingConfiguration.RegisterWellKnownClientType(
typeof(RemoteTestClass), "http://localhost:8989/RemoteTestClass.rem");
}
}

// 通知OKのメソッド
[WebMethod]
public string RemoteTestString(string i_string)
{
try
{
// リモートクラスをインスタンス化
c_testClass = new RemoteTestClass();

c_testClass.TestString = i_string;
c_testClass.CallService(enProcessType.RemoteTestClass);

return c_testClass.Response;
}
catch (Exception ex)
{
//エラー処理;
return ex.ToString();
}
}

// 通知OKのメソッド
[WebMethod]
public string RemoteTestTest(RemoteTestClass i_testClass)
{
try
{
// リモートクラスをインスタンス化
c_testClass = new RemoteTestClass();

c_testClass.TestString = i_testClass.TestString;
c_testClass.CallService(enProcessType.RemoteTestClass);

return c_testClass.Response;
}
catch (Exception ex)
{
//エラー処理;
return ex.ToString();
}
}

// 通知不可のメソッド
[WebMethod]
public string RemoteTestParent(ParentClass i_parentClass)
{
try
{
// リモートクラスをインスタンス化
c_parentClass = new ParentClass();

c_parentClass.Child = i_parentClass.Child;
c_parentClass.CallService(enProcessType.ParentClass);

return c_parentClass.Response;
}
catch (Exception ex)
{
//エラー処理;
return ex.ToString();
}
}
}
◎Windowsサービス側のコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using RemoteTest;

namespace RemoteTestService
{
public partial class TestService : ServiceBase
{
// 受信チャンネルを設定
HttpChannel channel = new HttpChannel(8989);

// リモートクラス定義
ParentClass c_parentClass;
RemoteTestClass c_testClass;

/// <summary>
/// コンストラクタ
/// </summary>
public TestService()
{
// リモートクラスをインスタンス化
c_parentClass = new ParentClass();
c_testClass = new RemoteTestClass();

if (ChannelServices.RegisteredChannels.Length == 0)
{
// チャンネルを登録
ChannelServices.RegisterChannel(channel, false);

// RemotingServiceをMarshal
ObjRef oref1 = RemotingServices.Marshal(c_parentClass, "ParentClass.rem");
ObjRef oref2 = RemotingServices.Marshal(c_testClass, "RemoteTestClass.rem");

// イベントを登録
c_parentClass.CalledClient += new ParentClass.CalledClientHandler(RemoteCalled);
c_testClass.CalledClient += new RemoteTestClass.CalledClientHandler(RemoteCalled);
}

InitializeComponent();
}

/// <summary>
/// サービス起動メソッド
/// </summary>
/// <param name="args">起動時に使用するパラメータ(未使用)</param>
protected override void OnStart(string[] args)
{
// TODO: サービスを開始するためのコードをここに追加します。

// リスナー開始
channel.StartListening(null);
}

/// <summary>
/// サービス停止メソッド
/// </summary>
protected override void OnStop()
{
// TODO: サービスを停止するのに必要な終了処理を実行するコードをここに追加します。
}

/// <summary>
/// リモート処理起動メソッド
/// </summary>
/// <param name="i_processType">起動する処理タイプ</param>
public void RemoteCalled(enProcessType i_processType)
{
switch (i_processType)
{
case enProcessType.ParentClass:
{
c_parentClass.Response = c_parentClass.Child.InClass.XString;

break;
}
case enProcessType.RemoteTestClass:
{
c_testClass.Response = c_testClass.TestString;

break;
}
}
}
}
}

以上。

Webサービス上でRemoteTestStringメソッド、RemoteTestTestメソッドを呼んだ場合にはイベントが通知され、Responseが返りますが、RemoteTestParentメソッドを呼んだ時にはイベントがハンドルされません。

ParentClass、RemoteTestClassのCallServiceメソッド内のCalledClientのNullチェックの箇所にブレークポイントを貼ると、RemoteTestParentメソッドの時にはCalledClientはNullの状態でした。

サンプルで作成し、現象再現も確認しています。
これでお分かり頂けますでしょうか?

よろしくお願い致します。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-05-25 19:58
「ドメイン間通信のスキマに挑戦!」みたいなソースですね。
ChildClassとInnerClassにはSerializableAttributeが付いてるはずですね。単なるソースの書き忘れですよね?

実行した結果としては、私の環境ではRemoteTestParent()からでもRemoteTestClass.CallService()が呼ばれました。

RemoteTestTest()を
コード:

public string RemoteTestTest(RemoteTestClass i_testClass)
{
try
{
// リモートクラスをインスタンス化
c_testClass = new RemoteTestClass();
return c_testClass.TestString;
}
catch (Exception ex)
{
//エラー処理;
return ex.ToString();
}
}


c_testClassに新しいインスタンスを設定したらすぐにそのc_testClassのTestStringを返すようにしてください。
そして、Webサービスの呼び出しを
コード:

Service service = new Service();

service.RemoteTestString("abc");

RemoteTestClass rtc = new RemoteTestClass();
rtc.TestString = "def";
service.RemoteTestTest(rtc);


こうしてください。
RemoteTestTest()の返り値が"def"になるのが分かると思います。
RemoteTestTest()の中のc_testClassとi_testClassは同じオブジェクト(へのプロキシ)だということは理解していますか?
いや、多分理解していらっしゃらないので
c_testClass.TestString = i_testClass.TestString;
このような無駄な行を書いているのだと思いますが。
さらに、上のWebサービス呼び出しのソースで"def"を設定している部分を
rtc.TestString = null;
にすると、RemoteTestTest()の返り値が"abc"になるでしょう。面白いですね。
多分Webサービスの呼び出しで渡す引数をシリアライズする時に値が設定されていない項目については含めないようにしているんでしょうね。おそらくパフォーマンス的な問題で。すると、前回の呼び出しで設定した"abc"が取得されるということです。

なぜ私の環境で出来て、HANさんの環境でできないのかは分からないのですが、Webサービスの引数の型を、ParentClassやRemoteTestClassではない型を使うようにすれば解決すると思います。……多分。
Microsoftも「Webサービスの引数の型はintやstringなどの単純な型(プリミティブ型?)にしよう」と言っていますし。
ましてやリモーティングのプロキシとなるクラスを引数にというのは……まぁそれが「スキマに挑戦」と言った理由です。内部でどんな動きをしているのか想像がつかないですね。

[ メッセージ編集済み 編集者: 一郎 編集日時 2007-05-25 20:09 ]
HAN
会議室デビュー日: 2007/05/24
投稿数: 5
投稿日時: 2007-05-28 11:44
一郎さん、
ご丁寧な解説恐れ入ります。

>ChildClassとInnerClassにはSerializableAttributeが付いてるはずですね。単なるソースの書き忘れですよね?

そうですね。SerializableAttributeを忘れていました。

RemoteTestClass.CallService()が呼ばれるところまではデバッグでも確認できているのですが、プリミティブ型ではない場合に、CallService内のCalledClientがNullの状態になってしまうところで引っかかっています。
一郎さんの環境ではCallServiceメソッド内でCalledClientは動きましたでしょうか?

今回このような課題にぶつかった経緯として、

◎複数階層(各階層の要素数は可変)のデータをWebサービスで受け取り、同期的にWindowsサービスに渡す。

という必要があり、

@Webサービスのメソッドが呼ばれた時点でWindowsサービスにイベントにより通知する。
AWindowsサービスはRemotingのインスタンスを参照し、その中身の情報を利用する。
BWindowsサービス側での処理結果をWebサービスはSOAPクライアントに返す。

という形での実現を模索していました。

複数階層のデータ(例:Aの下にBが複数個、Bの下にはCが複数個のツリー構造)を使用せねばならず、そのために最終的にはクラスを配列やArrayListを用いて入れ子にすることを考えています。(もっと単純な通信データの仕様ならば良かったのですが(本音))

ParentClassにイベントCalledClientが定義され、Windowsサービス側でハンドラが追加されているという点では、プリミティブ型でもそうでない場合でも同じであるにも関わらず、プリミティブ型でない場合に限って、Webサービス側から参照したCalledClientがNullになってしまう点がよく分からないでいます。双方のサービスのクラスインスタンスがリンクされていない状態なのでしょうか。

こういう使い方はできないものなのか、また、ツリー構造データをSOAP経由でWebサービスで受け取り、なおかつWindowsサービスに渡すという目的に対し他に代用できるアイデアなどもしお持ちであればアドバイス願えれば幸いです。

何度も申し訳ありませんが、よろしくお願い致します。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-05-29 11:57
引用:

HANさんの書き込み (2007-05-28 11:44) より:
一郎さんの環境ではCallServiceメソッド内でCalledClientは動きましたでしょうか?


動きました。…と思います。
何か設定違いや勘違いをしているかもしれませんが。

引用:

HANさんの書き込み (2007-05-28 11:44) より:
◎複数階層(各階層の要素数は可変)のデータをWebサービスで受け取り、同期的にWindowsサービスに渡す。

という必要があり、

@Webサービスのメソッドが呼ばれた時点でWindowsサービスにイベントにより通知する。
AWindowsサービスはRemotingのインスタンスを参照し、その中身の情報を利用する。
BWindowsサービス側での処理結果をWebサービスはSOAPクライアントに返す。

という形での実現を模索していました。


想像でしかありませんが、おそらく問題はWebサービスの引数にWebサービス上でリモーティングのプロキシとして扱うクラスを使用していることではないかと思います。
例えばWebサービスのRemoteTestParent()メソッドの引数の型はParentClassで、呼び出す側ではParentClassのオブジェクトを渡すわけですが、受け取ったi_parentClass変数が指しているオブジェクトの型はParentClassじゃあないですよね。

こういうのはどうでしょうか。WebサービスのクライアントからWebサービスへはHANさんの作った「複数階層のデータ」を引数で渡しているわけですから、WebサービスからWindowsサービスへも引数的に渡すというのは。
コード:
public class RemotingClass : MarshalByRefObject
{ 
	public delegate void CalledClientHandler(enProcessType processType); 
	public event CalledClientHandler CalledClient; 

	private ParentClass c_parent
	private string c_response; 

	public ParentClass Parent
	{ 
		get{ return c_parent; } 
		set{ c_parent= value; } 
	} 

	public string Response 
	{ 
		get { return c_response; } 
		set { c_response = value; } 
	} 

	public void CallService(enProcessType i_bsProcessType)
	{ 
		if (CalledClient != null) 
		{ 
			// Webサービスからのイベントを通知 
			CalledClient(i_bsProcessType); 
		} 
	} 
}


ParentClassそのものをリモーティングの対象にするのではなく、単に値としてやり取りするんです。

このコードは「ParentClassそのものをリモーティングしない」ということを表現することを目的としたもので、ParentClassをプロパティにしているのはおかしいですし、ほぼ同時にWebサービスが呼ばれるとある人のParentClassの値が別の人のWebサービスの動作に影響を与える可能性があったり(これはHANさんのコードもそうですね)と問題があるのでそのままは使えないですけど。
HAN
会議室デビュー日: 2007/05/24
投稿数: 5
投稿日時: 2007-05-29 14:13
一郎さん、
毎度毎度、丁寧に返信頂き感謝!です。

アドバイス頂いたように、別途Remoting専用のクラスを定義し、その内部にParentClassを持たせた場合、イベント駆動が確認できました!
クラスが、Webサービスのインスタンス(Proxy)、Windowsサービスのインスタンス、そして引数として受け取ったインスタンスの関係性がややこしくなっていたので、見栄えとしてもかえってスッキリしたと思いました。

現状ですと、ご指摘の通り、マルチスレッド動作はできませんね。
今のところSOAPクライアントは1台のみで、Webメソッドの処理中に同時起動することはありえないとは思いますが、キューに格納するなどの、シーケンス処理についても検討するつもりです。

足掛け何日もお付き合い頂き、本当にありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)