SoapSuds工具的作用在此就不多说了。简单地说,就是产生程序集的元数据,而这主要使用在Remoting架构中
。在Remoting中,为了达到客户端和服务端对远程对象元数据的分离,使用SoapSuds工具产生远程对象的元数
据,这样在客户端就不用引用远程对象的程序集了。
下面列举了几种使用SoapSuds的例子,其中有几个有趣的现象,我也会描述出来:
1、对SAO对象:
Server端采用通常的实现代码:
TcpChannel chan = new TcpChannel(9999);
ChannelServices.RegisterChannel(chan);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObj),
"RemoteObject", WellKnownObjectMode.Singleton);
Console.WriteLine("RemoteObject is Registered.");
Client端也采用通常的实现代码:
RemoteObj obj = (RemoteObj)Activator.GetObject(typeof(RemoteObj),
"tcp://localhost:9999/RemoteObject");
Console.WriteLine(obj.test());
远程对象RemoteObject的功能很简单,返回字符串:
public class RemoteObj : MarshalByRefObject
{
public RemoteObj()
{ }
public RemoteObj(string a)
{ }
public string test()
{
return "test";
}
}
现在代码完成了。首先编译RemoteObject的代码。然后打开.Net Framework Command Prompt,这里我们假定编
译后的程序集就在bin\debug目录下。用cd命令进入到该目录,然后运行如下的命令:
soapsuds -ia:RemoteObject -oa:meta.dll
-ia参数表明输入程序集是RemoteObject,也就是说要从这个程序集中提取元数据。注意这里不能加任何后缀如
.dll,.exe,否则会出错
-oa参数表明输出程序集会存放在该目录下的meta.dll文件中,也就是说这个程序集中就是提取出来的元数据了
好了,现在在Client项目里面加上对这个meta.dll的引用,然后分别编译Client和Server,顺序无所谓。
最后执行,先执行Server.exe,看到输出“RemoteObject is Registered.”后再执行Client.exe,如果看到输
出了“test”,那么说明成功了。
对于SAO对象,是否加上-nowp选项对结果没有什么影响,至少我使用的时候是这样。如果有谁知道更多信息,
请告诉我:)
2、对CAO对象
Server端代码如下:
TcpChannel chan = new TcpChannel(9999);
ChannelServices.RegisterChannel(chan);
RemotingConfiguration.ApplicationName = "RemoteObject";
RemotingConfiguration.RegisterActivatedServiceType(typeof(RemoteObj));
Console.WriteLine("RemoteObject is Registered.");
Client端代码也要做相应修改,我们采取如下的方式(当然还有其它方式):
RemotingConfiguration.RegisterActivatedClientType(typeof(RemoteObj),
"tcp://localhost:9999/RemoteObject");
RemoteObj obj = new RemoteObj();
Console.WriteLine(obj.test());
远程对象,我们还是首先使用上面产生的那个程序集,也就是WrappedProxy。
好了,现在还是按照刚才那样,依次启动Server和Client,你可以看到预料中的“test”输出到了客户端。
到此为止,基本的SoapSuds使用已经结束了。下面说些题外话:
1、对于CAO,是否加上-nowp选项对结果的一个小小的影响
我们在客户端代码中注释掉这一行:
RemotingConfiguration.RegisterActivatedClientType(typeof(RemoteObj),
"tcp://localhost:9999/RemoteObject");
也就是去掉在客户端进行注册这一过程。现在如果我们使用的是WrappedProxy,也就是用下面的命令产生的程
序集:
soapsuds -ia:remoteobject -oa:meta.dll
这时我们启动Client会得到如下的输出:
System.NullReferenceException: Object reference not set to an instance of an object.
我们可以通过查看产生的源代码来分析,用下面的命令产生元数据的源代码:
soapsuds -ia:remoteobject -gc
这里我们使用-gc选项,而不是-oa选项,意思是产生源代码,而不是程序集。我们打开这个产生的源代码文件
,可以看到下面的代码(我省略了一些附加的跟我要说明的无关的信息):
public class RemoteObj : System.Runtime.Remoting.Services.RemotingClientProxy
{
public RemoteObj()
{
System.Runtime.Remoting.SoapServices.PreLoad(typeof(RemoteObject.Properties.Settings));
}
public Object RemotingReference
{
get{return(_tp);}
}
public String test()
{
return ((RemoteObj) _tp).test();
}
}
对此我的理解是:这个RemoteObj类是从System.Runtime.Remoting.Services.RemotingClientProxy继承下来的
,因此调用new操作符的时候会试图在服务端产生远程对象的新实例。但是因为我们根本没有在客户端注册,所
以客户端不知道服务端在哪里,也就不知道在哪里创建远程对象了,所以使用new操作符产生的对象就是一个空
引用null,从而导致出现这个异常。
而如果使用的是NonWrappedProxy,也就是用下面的命令产生的程序集:
soapsuds -ia:remoteobject -oa:meta.dll -nowp
我们不改变Client代码重新编译Client后运行,会发现没有异常,而只是输出了一个空行。我们同样产生源代
码来分析:
soapsuds -ia:remoteobject -gc -nowp
观察结果:
public class RemoteObj : System.MarshalByRefObject
{
public String test()
{
return((String) (Object) null);
}
}
可以看到,现在RemoteObj类是直接从MarshalByRefObject类继承。所以我猜测调用test方法时会先尝试创建远
程对象的实例,但是由于没有在客户端注册,因此无法创建,这时就会调用本地的return((String) (Object)
null)。所以就返回了null,从而可以在Console中看到输出了一个空行。
这是我发现的第一个有趣的现象。
2、如果我们将Server的实现和远程对象的实现放在一个程序集中(虽然实际工作中这种情况比较少见),也有
一些要注意的地方。
我们将Server.cs文件改成如下这样:
class SomeRemoteObject : MarshalByRefObject
{
public void doSomething()
{
Console.WriteLine("SomeRemoteObject.doSomething() called");
}
}
class ServerStartup
{
static void Main(string[] args)
{
TcpChannel chnl = new TcpChannel(1234);
ChannelServices.RegisterChannel(chnl);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(SomeRemoteObject),
"SomeRemoteObject",
WellKnownObjectMode.SingleCall);
Console.WriteLine("ServerStartup.Main(): Server started");
Console.ReadLine();
}
}
客户端使用如下的代码:
static void Main(string[] args)
{
SomeRemoteObject obj = (SomeRemoteObject)Activator.GetObject(
typeof(SomeRemoteObject),
"tcp://localhost:1234/SomeRemoteObject");
obj.doSomething();
Console.ReadLine();
}
现在该如何产生元数据程序集呢?
如果我们还是按照先前的思路,用如下的命令:
soapsuds -ia:server -oa:meta.dll
那么我们在客户端添加对meta.dll的引用后,编译会出现如下的错误:
The type or namespace name 'SomeRemoteObject' could not be found (are you missing a using
directive or an assembly reference?)
为什么会这样呢?我们还是产生源代码来看看
soapsuds -ia:server -gc
结果里竟然没有我们的SomeRemoteObject类的代码(为什么会这样?我也不知道)。那么我们是不是就没有办
法了呢?当然不是,别忘了SoapSuds还有个参数-types,可以指定产生哪个类的元数据。所以我们用下面的命
令来产生元数据程序集:
soapsuds -types:Server.SomeRemoteObject,Server -oa:meta.dll
-types后面紧接的是要产生元数据的类的完整名称,包括命名空间,而且大小写也要一致,逗号后面跟上程序
集的名称,大小写无所谓,而且也不能包含.dll或.exe后缀。
然后我们将这个引用添加到客户端,再编译。运行,可以看到在服务端输出了“SomeRemoteObject.doSomethin
g() called”这一行,成功了。
我们仍然可以观察产生的源代码来看看:
soapsuds -types:Server.SomeRemoteObject,Server -gc
结果如下:
public class SomeRemoteObject : System.Runtime.Remoting.Services.RemotingClientProxy
{
public SomeRemoteObject()
{
}
public Object RemotingReference
{
get{return(_tp);}
}
public void doSomething()
{
((SomeRemoteObject) _tp).doSomething();
}
}
3、还是当Server的实现和远程对象的实现在一个程序集中的情况,这次我们使用Http通道,你会发现有一些不
同的哦。
对Server代码做小小修改:
HttpChannel chnl = new HttpChannel(1234);
ChannelServices.RegisterChannel(chnl);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(SomeRemoteObject),
"SomeRemoteObject.soap",
WellKnownObjectMode.SingleCall);
Console.ReadLine();
相应的Client代码也要小小修改:
SomeRemoteObject obj = (SomeRemoteObject)Activator.GetObject(
typeof(SomeRemoteObject),
"http://localhost:1234/SomeRemoteObject.soap");
obj.doSomething();
我们除了上面2中所介绍的方法之外,还可以用另一种方法来产生元数据——就是SoapSuds的-url选项。
我们用下面的命令来产生元数据程序集:
soapsuds -url:http://localhost:1234/SomeRemoteObject.soap?wsdl -oa:meta.dll
注意写法:-url后面跟上的是远程对象的url,注意在SomeRemoteObject.soap后面加上了?wsdl(为什么要这样
写?天晓得)。
结果如下:
Error: Unable to retrieve schema from url: http://localhost:1234/SomeRemoteObjec
t.soap?wsdl, Unable to connect to the remote server, 不能做任何连接,因为目标机
器积极地拒绝它。
怎么出错了?从错误提示我们可以猜测到,是因为没有启动Server。原来SoapSuds在提取元数据的时候需要找
到远程对象。因为我们用的是-url参数,所以SoapSuds会从这个URL中去提取元数据,但是我们都没有启动Serv
er,自然SoapSuds找不到远程对象。
好了,我们启动Server后,再执行SoapSuds,OK。然后我们在客户端添加对meta.dll的引用,编译,执行Clien
t,一切都按照我们预想的出来了。
以上都是我在.Net Framework 2.0 beta环境下实际操作后的结果,当然可能会有不正确的地方。希望高人能够
不吝赐教。
另外,Rickie和Wayfarer的两篇blog中叙述了SoapSuds工具的一些不足。
我个人觉得主要的不足就是不能提取出带参数的构造函数,我们只能通过产生源代码,然后手动去修改