SoapSuds工具使用初探

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环境下实际操作后的结果,当然可能会有不正确的地方。希望高人能够

不吝赐教。
另外,RickieWayfarer的两篇blog中叙述了SoapSuds工具的一些不足。
我个人觉得主要的不足就是不能提取出带参数的构造函数,我们只能通过产生源代码,然后手动去修改

posted on 2005-01-05 03:45  buaaytt  阅读(2008)  评论(1编辑  收藏  举报

导航