COM+ Web 服务:通过复选框路由到 XML Web Services


(来源:http://www.microsoft.com/china/msdn/library/dndotnet/html/comwscheckb.asp)


John Noss 和 Jonathan Hawkins
Microsoft Corporation
2001年11月

摘要:COM+ Web 服务新增了某些功能,可与 Microsoft .NET Remoting 集成,并通过 SOAP for COM+ 组件实现 XML Web Services 发布的复选框激活。本文通过几个示例介绍基本的互操作性、配置以及托管和非托管 COM+ 组件(在 Microsoft Windows .NET Server 和 Microsoft Windows XP Professional 上作为 XML Web Services 发布)的部署。

目录

简介

COM+ Web 服务新增了某些功能,可与 Microsoft .NET Remoting 集成,并通过 SOAP for COM+ 组件实现 XML Web Services 发布的复选框激活。本文通过几个示例介绍基本的互操作性、配置以及托管和非托管 COM+ 组件(在 Microsoft Windows .NET Server 和 Microsoft® Windows® XP Professional 上作为 XML Web Services 发布)的部署。还通过示例介绍了几个新功能,使运行 Windows XP 的客户端能够访问远程服务器上的 XML Web Services。

当开发人员使用 .NET Remoting 和托管代码来完善现有的非托管 COM+ 服务器和客户端代码时,这些功能可以帮助他们利用和简化迁移过程。在 .NET 框架的测试阶段,有许多用户问及如何配置 .NET Remoting 来进行简单的跨计算机激活操作。COM+ Web 服务的解决方案是同时自动配置服务器 (Microsoft Windows .NET Server) 和客户端 (Microsoft Windows XP Professional) 计算机,使用 .NET Remoting 来提供 SOAP 以替代 DCOM。

Microsoft Windows XP 和 Microsoft .NET 框架是本年度发布的两个最重要的软件。二者的目的都是为了简化过程并提高软件开发人员的能力,因此利用这两种产品并发挥各自优势来提供一套集成的、易于使用的解决方案就成为自然之选。COM+ Web 服务提供了一种简单的方法,可以将 COM+ 组件发布为 XML Web Services ;还提供了新的集成功能,用于从客户端计算机访问 XML Web Services。从以下 Microsoft Visual Basic Scripting Edition (VBScript) 示例中可以了解其易于使用的特点,该示例用于确定 Alaska 的 Fairbanks 的当前气温。请在 Windows XP(已安装 .NET 框架)或 Windows .NET Server 上运行此示例:

set SoapObj = GetObject
   ("soap:wsdl=http://www.xmethods.net/sd/TemperatureService.wsdl")
WScript.Echo "Fairbanks 气温 = " & SoapObj.getTemp("99707")

在上面的示例中,服务器是在 Linux 上运行的 Apache SOAP Server,但也可以使用任何具有标准 Web 服务说明语言 (WSDL) 描述功能的 SOAP V1.1 服务器。

注意: 如果出现“未找到服务器”错误,您需要在控制面板的 Internet 选项中手动配置防火墙设置。

使用 SOAP 作为计算机之间通信协议的优点之一,在于它增加了可以交互操作的计算机的种类。.NET Remoting 具有以下两种基本操作模型:

  • 已知对象 (WKO): WKO 是 SOAP V1.1 所支持的最常见的 XML Web Services 模型。它允许与其他运行 SOAP V1.1 兼容堆栈的计算机协同工作。服务器和客户端可以是运行 Apache SOAP 的非 Windows 服务器和运行 pocketSOAP 的 Pocket PC,也可以是基于 Windows 的服务器和客户端。唯一的要求是服务器上必须安装了与 WSDL 1.1 版本兼容的描述功能,以便生成相应的代理。此代理是在运行时生成的,并且第一次使用 WSDL 名字对象时没有用户介入。
  • 客户端激活的对象 (CAO): CAO 提供了更丰富的开发环境,包括稳定持久的连接。较之典型的 XML Web Services 模型,它更类似于 DCOM 模型,但要求在服务器和客户端上都安装有某一版本的 .NET 框架。

COM+ Web 服务可以使用 WKO 和 CAO 两种激活模型,而且所有的服务器应用程序都可以提供 WKO 和 CAO 端点。通过组合使用激活模型、XML Web Services 和 .NET Remoting,开发人员可以轻松地组合和匹配托管及非托管的客户端和服务器。下表所示为两种激活模型所支持方案的示例。

表 1:WKO 模型支持的方案

WKO 客户端 WKO 服务器
VB 6.0 或非托管 C++ VB 6.0 或非托管 C++
VB 6.0 或非托管 C++ VB .NET 或 C#
VB 6.0 或非托管 C++ SOAP V1.1(在 WSDL 中描述)
VB 6.0 或非托管 C++ Microsoft SOAP(ATL Server,SOAP TK)
C# 或 VB .NET SOAP V1.1(在 WSDL 中描述)
C# 或 VB .NET VB 6.0 或非托管 C++
C# 或 VB .NET VB .NET 或 C#
C# 或 VB .NET Microsoft SOAP(ATL Server,SOAP TK)
Microsoft SOAP Toolkit V2.0 VB 6.0 或非托管 C++
Microsoft SOAP Toolkit V2.0 C# 或 VB .NET
SOAP v1.1 VB 6.0 或非托管 C++
SOAP v1.1 C# 或 VB .NET

表 2:CAO 模型支持的方案

CAO 客户端 CAO 服务器
C# 或 VB .NET(早期绑定) VB 6.0 或非托管 C++
VB 6.0 或非托管 C++ VB 6.0 或非托管 C++
VB 6.0 或非托管 C++ C# 或 VB .NET
C# 或 VB .NET C# 或 VB .NET

这种新的 COM+ Web 服务适用于以下用户:

  1. 当前安装有 Microsoft® Visual Basic® 6.0 或者非托管 Microsoft Visual C++® COM+ 应用程序(需要通过防火墙才能进行某些激活操作)的 COM+ 用户。(使用 SOAP 并不排斥通过 DCOM 访问服务器上的相同组件,客户端计算机可以选择协议。)对于这些客户来说,如果要使用 SOAP 而不是 DCOM,则使用客户端代理导出和 CAO 模型都不需要更改客户端和服务器应用程序。只需要在服务器应用程序上启用 SOAP,将其作为客户端代理导出,然后将代理安装在要用作 SOAP 客户端的 Windows XP 计算机上。
  2. 完全迁移到 Windows XP 和 Windows .NET Server 上的托管代码的公司。COM+ Web 服务有助于在连接的两端设置远程端点。
  3. 需要在上述两种方案中组合和匹配各种服务的开发人员,以及编写托管服务器组件、或带有非托管服务器组件的托管客户端应用程序的开发人员。在第二种情况中,开发人员可以利用 COM+ Web 服务,在用托管代码替换之前充分利用早期的非托管组件。

简单的已知对象 (WKO) 示例

除了对 Linux 和 ApacheIn 提供 SOAP 支持外,将 COM+ Web 服务应用于其他 Microsoft 产品(例如 ATL Server Web 服务)也非常简单。只需使用 Microsoft Visual Studio® .NET 在服务器上生成、编译和部署默认的 ATL Web 服务即可。对它进行访问的客户端代码如下(请用驻留 ATL Server 应用程序的 Web 服务器名称替换 MyServer,用您的 ATL Server DLL 的名称替换 JALTServer):

mon="soap:wsdl=http://MyServer/JALTServer/JALTServer.dll?
   Handler=GenJALTServerWSDL"
set c = GetObject(mon)
WScript.Echo c.HelloWorld("COM+ Web 服务") 

上述示例简单说明了 Microsoft Windows XP 和 Microsoft Windows .NET 服务器所包含的一个新的 SOAP 名字对象。

数据发布

如果您只想提供数据而不是使用数据,那么只需选择一个复选框,然后输入 IIS 虚拟根名称的值。要创建完整的 COM+ Web 服务,请执行以下步骤:

使用 Visual Basic 6.0 创建简单的 Microsoft ActiveX® DLL,并输入以下代码:

Function Add(ByVal Value1 As Double, ByVal Value2 As Double) As Double
 Add = Value1 + Value2
End Function

在 Visual Basic 项目属性页上的 General选项卡中,设置 Unattended ExecutionRetained in Memory,并在 Component 选项卡中选择 Remote Server Files。使用 Visual Basic 开发环境生成此 DLL。

在创建 Visual Basic 应用程序之后,需要将其注册为 COM+ 应用程序。启动组件服务管理工具,在 Windows XP 上创建 COM+ 应用程序。(在此示例中,应用程序被命名为 VB6Soap。)将所创建的 DLL 作为组件导入,然后浏览到 COM+ 应用程序属性页的 Activation 选项卡,选择 Uses SOAP,输入一个 SOAP Vroot(例如,VB6Soap),然后单击 OK(如图 1 所示)。

图 1:VB6Soap COM+ 应用程序属性页

现在,应用程序已发布为 XML Web Services,并且可以使用 SOAP 激活。使用 Internet Explorer 浏览到 http://localhost/VB6Soap/default.aspx,您会在该 aspx 页上发现一个超链接,通过它可以链接到您的组件所生成的 WSDL。以下 VBScript 将激活您的组件:

set c = GetObject
   ("soap:wsdl=http://localhost/VB6Soap/VB6Soap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

如果用您的服务器名称替换上面脚本中的 localhost,它也可以在远程客户端计算机上正常工作。引用的页面(在此示例中为 VB6Soap.Calc.soap)是以 .soap 后缀结尾的组件 ProgID。

要通过 SOAP Toolkit(与 Windows XP Professional 一起提供,并且不使用 .NET Remoting)访问同一端点,请运行以下 VBScript:

set c = CreateObject("MSSOAP.SOAPClient")
c.mssoapinit("http://localhost/VB6Soap/VB6Soap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

要简化在服务器上发布 SOAP 的过程,您可以使用 Microsoft C#™ 或 Visual Basic .NET,并从 ServicedComponent 继承。下面是简单托管组件的托管代码示例:

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.EnterpriseServices;

[assembly: ApplicationName("CSSoap")]
[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="CSSoap")]
[assembly: AssemblyKeyFile("CSSoap.snk")]
namespace CSSoap
{
  public interface ICalc
  {
   double Add (double Value1, double Value2);
  }

  [ClassInterface(ClassInterfaceType.AutoDual)]
  [TransactionAttribute(TransactionOption.None)]
  public class Calc : ServicedComponent, ICalc
  {
   public double Add (double Value1, double Value2);
   {
      return (Value1 + Value2);
   } 
  }
}

上述示例中值得注意的是 ApplicationActivation 属性:

[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="CSSoap")]

建立 C# 组件,将它安装在全局程序集缓存中,然后运行 regsvcs.exe 将它注册为 COM+ 应用程序。这样,该组件就被发布为 IIS 虚拟根和 SOAP 端点。要成功地远程使用 ServicedComponent,还需要使用 gacutil.exe 或 .NET 框架用户界面,将这个编译后的程序集放入全局程序集缓存 (GAC) 中。要通过 WSDL 访问此 SOAP 端点,请使用以下 VBScript:

set c = GetObject
   ("soap:wsdl=http://localhost/CSSoap/CSSoap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

作为 SOAP 交互操作性的简单示例,SOAP Toolkit 与 Windows XP Professional 一起提供,并且即使运行 Windows XP 的客户端计算机上没有安装 .NET 框架,使用以下 VBScript 也可以访问 COM+ SOAP 端点:

set c = CreateObject("MSSOAP.SOAPClient")
c.mssoapinit("http://localhost/CSSoap/CSSoap.Calc.soap?WSDL?
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

为简单起见,上述示例全部使用 VBScript 来访问 Web 服务。其实也可以通过 SOAP WSDL 名字对象使用 Visual C+、Visual Basic 6.0、Visual Basic .NET 或 C# 进行编写。例如,Visual Basic .NET 也可以使用编译的托管代码访问同一对象,如下例所示:

Imports System
Imports System.Runtime.InteropServices
Module WKOClient
 Sub Main()
    Dim WSDLMoniker = 
      "soap:wsdl=http://localhost/CSSoap/CSSoap.Calc.soap?WSDL"
      Dim obj as Object
   obj = Marshal.BindToMoniker(WSDLMoniker)
   Console.WriteLine(obj.Add(1,2))
 End Sub
End Module

使用 VBScript 是为了表明,托管的和非托管的客户端都可以访问发布为 COM+ Web 服务的 COM+ 组件。在大型组织或应用程序中,很难一次转换所有部分,COM+ Web 服务允许将一部分应用程序转换为托管代码,而无需立即全面重新编写现有的应用程序。

简单的客户端激活对象 (CAO) 示例

服务器上的 COM+ Web 服务发布将每一组件发布为 WKO 和 CAO 两种形式,因此不需要额外的服务器配置。在服务器上唯一要做的操作是,在选择 Uses SOAP 复选框(位于 COM+ 应用程序属性页的 Activation 选项卡上)并在 SOAP VRoot 文本框中输入值以后,将 COM+ 应用程序作为代理程序导出。下面显示了导出代理应用程序的必要步骤:

  1. 用鼠标右键单击组件服务管理工具中的 VB6Soap COM+ 应用程序,并选择 Export,如图 2 所示。

    图 2:组件服务管理工具

  2. 在图 3 所示的 COM+ 应用程序导出向导中,输入代理 .msi 文件的位置和名称。

    图 3:COM+ 应用程序导出向导

  3. 将代理 .msi 文件安装在单独的客户端计算机上,作为预先生成的 COM+ 应用程序。

    安装时将对代理进行适当的配置,以便通过 SOAP 访问正确的服务器和虚拟根。对于客户端激活,可以不使用 WSDL 名字对象,而使用常规非托管的 COM+ 激活(例如,CoCreateInstanceCreateObject 等)。在服务器上创建并在单独的客户端计算机上安装上述 Visual Basic 计算器示例的应用程序代理后,以下 VBScript 将通过 SOAP 访问该服务器:

    set c = CreateObject("VB6Soap.Calc")
    for i = 1 to 10
     WScript.Echo i & " " & c.Add(i,i) & " " & Time
    next 
    

    如果代理程序没有启用 COM+ Web 服务,则上述 VBScript 代码将使用 DCOM 访问服务器应用程序。

事务性组件示例

简单的计算器远算不上工作量繁重的业务应用程序,因此我们现在考虑带有对象池的适于 COM+ 事务性组件的应用程序。

最容易管理和配置的组件是由 ServicedComponent 导出的托管代码组件,如以下 C# 示例所示:

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Data;
using System.Data.SqlClient;

[assembly: ApplicationName("SCTrans")]
[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="SCTrans")]
[assembly: AssemblyKeyFile("SCTrans.snk")]
namespace SCTrans
{
  public interface ISCTrans
  {
   string CountUp (string Key);
  }

  [ObjectPooling(MinPoolSize=0, MaxPoolSize=25)]
  [JustInTimeActivation(true)]
  [ClassInterface(ClassInterfaceType.AutoDual)]
  [TransactionAttribute(TransactionOption.RequiresNew)]
  public class SCTransSQLNC : ServicedComponent, ISCTrans
  {
   [AutoComplete]
   public string CountUp (string Key)
   {
      _command = new SqlCommand("", _connection);
      _command.CommandType = CommandType.Text;
      _command.Connection.Open();
     _command.CommandText = "UPDATE CallCount WITH (ROWLOCK) SET 
      CallCount = CallCount + 1 WHERE Machine='" + Key + "'";
     _command.ExecuteNonQuery();
      _command.Connection.Close();
     _numcalls++;
     return (_numcalls + " NC " + _guid);
   } 

   protected override bool CanBePooled()
   {
     return true; 
   }
   private int _numcalls = 0;
   private string _guid = Guid.NewGuid().ToString();
   private SqlConnection _connection = 
   new SqlConnection("user id=MyUser;password=My!Password;
   database=SoapTest;server=MyServer");
   private SqlCommand _command; 
   
  }
}

要建立并运行此 C# 组件,在完成编辑连接值以连接到 Microsoft SQL Server™ 数据库之后,需要使用 sn.exe 生成 sctrans.snk 加强名称关键字文件,然后在 using 语句中使用程序集引用对其进行编译。如果您在服务器上进行部署,应使用 gacutil.exe(如果正在使用 SDK)或通过 .NET 框架用户界面将程序集放入 GAC,然后运行 regsvcs.exe,注册 COM+ 托管组件。Regsvcs.exe 将使用以下属性,将组件发布为服务器上的 SOAP 端点和服务器(进程外)激活:

[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="CSSoapSQL")]

此组件在每种方法调用中使用不同的事务,具有一个自动完成方法,并被配置为进行缓冲。使用托管和非托管 COM+ 组件时,对象池和事务将如所预期的那样通过 SOAP 运行。例如,如果使用下列 VBScript 通过 SOAP 访问以下 ServicedComponent

mon = "soap:wsdl=http://jnoss3/sctrans/SCTrans.SCTransSQLNC.soap?WSDL"
WScript.Echo(mon)
for i = 1 to 2
 set c = GetObject(mon)
 for j = 1 to 10
  WScript.Echo i & " " & j & " " & c.CountUp("SCWKONC") 
 next
next

将显示以下输出内容:

C:\moniker>actscwko
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

soap:wsdl=http://jnoss3/sctrans/SCTrans.SCTransSQLNC.soap?WSDL
1 1 486 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 2 487 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 3 488 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 4 489 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 5 490 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 6  8 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
1 7  9 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
1 8 10 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
1 9 494 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 10 495 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 1 13 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 2 14 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 3 15 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 4 499 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 5 17 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 6 501 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 7 502 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 8 19 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 9 20 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 10 21 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 

这就是所预期的缓冲的组件:从缓冲池中拖出对象并重新使用。使用客户端激活的缓冲组件的行为都是相同的。

非托管组件的对象池和事务也如所预期的那样运行(虽然 Visual Basic 6.0 组件不支持对象池)。需要为大多数非托管应用程序通过 COM+ 管理工具设置缓冲和事务属性。

传递引用

WKO 与 CAO 模型的一个关键区别在于它们向有状态的对象传递引用的能力。以下是 C# ServicedComponent 示例,显示了此操作的基本步骤:

using System;
using System.Reflection;
using System.EnterpriseServices;
using System.Runtime.InteropServices;

[assembly: ApplicationName("RefPass")]
[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="RefPass")]
[assembly: AssemblyKeyFile("RefPass.snk")]
namespace RefPass
{
  public interface IParent
  {
    string SetRef(object inKid);
    object GetRef();
    string CountUp(object obj); 
  }

  public interface IChild
  {
    string GetValue ();
    string CountUp();
    void SetName(string key);
  }

  [ClassInterface(ClassInterfaceType.AutoDual)]
  public class Parent: ServicedComponent, IParent
  {
    protected Child _kid = null;

    public string SetRef(object inKid)
    {
      _kid = (Child)inKid;
      return _kid.GetValue();
    }

    public object GetRef()
    {
      return (object)_kid;
    }

    public string CountUp(object obj)
    {
      Child kid = (Child)obj;
      if (kid == null) return _kid.CountUp();
      else return kid.CountUp();
    }
 }

  [ClassInterface(ClassInterfaceType.AutoDual)]
  public class Child : ServicedComponent, IChild
  {
    private int _counter = 0;
    private string _name = "none";
    public string CountUp() { _counter++; return GetValue(); }
    public string GetValue() { return (_name + " " 
   +_counter.ToString()); }
    public void SetName(string key) { _name = key; }
  }
}

此 C# 程序有两个类:ChildParent。如果运行以下 VBScript 示例,WKO 与 CAO 模型的区别会更加明显:

set c1 = GetObject
   ("soap:wsdl=http://jnoss4/refpass/RefPass.Child.soap?wsdl")
set c2 = GetObject
   ("soap:wsdl=http://jnoss4/refpass/RefPass.Child.soap?wsdl")
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
C2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()

运行时将显示以下输出内容:

C:\moniker>refpasswko
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

none 1
none 1
none 1
none 1
none 1
none 1
none 1
none 1
none 1
none 1

名称和值说明了单一调用已知对象的无状态性质,因为组件是使用不同的方法调用创建的,所以方法调用之间不保留名称或值。

如果导出客户端代理,然后导入到另一台客户端计算机上,并且运行了下面的 VBScript,则 SOAP 激活将是 CAO 而不是 WKO:

'直接创建两个对象
set c1=CreateObject("RefPass.Child")
set c2=CreateObject("RefPass.Child")
'设置第一个对象的名称,并调用数次
'以递增对象内部计数器
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
'设置第一个对象的名称,并调用数次
'以递增对象内部计数器
c2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
'创建父对象
set p=CreateObject("RefPass.Parent")
'将子对象传递到父对象,并从父对象调用子对象
WScript.Echo p.SetRef(c1)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
'现在调用存储在父对象内部的子对象
dim c9
WScript.Echo p.CountUp(c9)
'从父对象获取该对象并直接调用
Set c3 = p.GetRef()
WScript.Echo c3.CountUp()

从命令行运行时,将显示以下输出内容:

C:\moniker>refpasscl
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

C1 1
C1 2
C1 3
C1 4
C1 5
C2 1
C2 2
C2 3
C2 4
C2 5
C1 5
C2 6
C2 7
C2 8
C2 9
C1 6
C1 7

即使在通过 SOAP 调用时,CAO 激活也会保留状态,并且允许通过 SOAP 来回传递对象引用。名称和值都保留在服务器上的类实例中,并且引用可以正确工作。这两种脚本都调用相同的编译 C# 组件,只是 .NET Remoting 激活模型不同。

除了使用 CreateObject 调用 CAO 激活外,还可以使用带有 COM+ 的名字对象,它可以提供 CAO 激活来替代 WKO(类型名称和程序集名字对象)。以下脚本:

'直接创建两个对象
set c1=GetObject("soap:typename=RefPass.Child,assembly=RefPass")
set c2=GetObject("soap:typename=RefPass.Child,assembly=RefPass")
'设置第一个对象的名称,并调用数次
'以递增对象内部计数器
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
'设置第二个对象的名称,并调用数次
'以递增对象内部计数器
c2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
'创建父对象
set p=GetObject("soap:typename=RefPass.Parent,assembly=RefPass")
'将子对象传递到父对象,并从父对象调用子对象
WScript.Echo p.SetRef(c1)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
'现在调用存储在父对象内部的子对象
dim c9
WScript.Echo p.CountUp(c9)
'从父对象获取该对象并直接调用
Set c3 = p.GetRef()
WScript.Echo c3.CountUp()

将显示以下输出内容:

C:\moniker>refpassca
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

C1 1
C1 2
C1 3
C1 4
C1 5
C2 1
C2 2
C2 3
C2 4
C2 5
C1 5
C2 6
C2 7
C2 8
C2 9
C1 6
C1 7

这与上面的 VBScript CreateObject(ProgID) 示例的输出内容相同。因为常规 COM+ 激活路径被 SOAP 代理应用程序截获,所以可以使用 CoCreateInstanceCreateInstance 以及其他传统的 COM+ 激活方法来调用使用 COM+ Web 服务的客户端激活的对象。

程序集和类型名称名字对象,对于从托管代码客户端远程获取预先配置的客户端激活也很有用,如下例所示:

Imports System
Imports System.Runtime.InteropServices
Module RefPassCl
Sub Main()
    Dim ChildMoniker = "soap:assembly=RefPass,typename=RefPass.Child"
    Dim ParentMoniker = "soap:assembly=RefPass,typename=RefPass.Parent"
    Dim c1,c2,p as Object
    c1 = Marshal.BindToMoniker(ChildMoniker)
    Console.WriteLine(c1.SetName("C1"))
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    c2 = Marshal.BindToMoniker(ChildMoniker)
    Console.WriteLine(c2.SetName("c2"))
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    p = Marshal.BindToMoniker(ParentMoniker)
    Console.WriteLine(p.SetRef(c1))
    Console.WriteLine(p.CountUp(c2))
    Console.WriteLine(p.CountUp(c2))
    Console.WriteLine(p.CountUp(c2))
    Console.WriteLine(p.CountUp(c2))
    Dim c9
    Console.WriteLine(p.CountUp(c9))
    Dim c3 = p.GetRef()
    Console.WriteLine(c3.CountUp())
 End Sub
End Module

编译并运行此 Visual Basic .NET 应用程序,将产生与前面两个 VBScript CAO 示例相同的输出内容。

因为服务器应用程序将组件发布为 CAO 和 WKO 两种形式,所以由远程客户端选择激活方法。虽然可能只对学术研究有意义,但是单一客户端计算机确实可以使用同一组件的两种远程激活方法,访问远程服务器上同一个 SOAP 发布的虚拟根。

SOAP 与 DCOM 的局限性和区别

.NET Remoting 的目的之一是提供丰富的分布式环境,使开发人员能够在此环境中对序列化协议(格式化程序)和网络协议(频道)进行组合与匹配。.NET 框架 1.0 版本中的 COM+ Web 服务仅支持一种格式化程序 (SOAP) 和一种频道 (HTTP)。这并不是说其他频道和格式化程序不能使用 ServicedComponents 或 COM+,而是说没有自动配置为这些备用频道和格式化程序提供客户端和服务器端点。

目前已经有用各种语言编写的大量 COM+ 组件。如果可以使用 COM+ Web 服务将所有这些组件启用为 Web 服务,那就太好了。但正如使用 .NET 框架 1.0 版本一样,并非所有现有的 COM 组件都可以使用 COM+ Web 服务。虽然多数具备类型库的现有组件可以正常工作,但是此版本不支持某些组件,例如 Windows 脚本组件 (WSC) 组件。某些复杂的类型库(其接口具有多重继承级别,或依赖于多个类型库)可能无法正常工作。此外,由于类型库转换的局限性,只有类型库中默认的接口才可以作为 Web 服务。

COM+ Web 服务并不是适用于所有现有非托管 COM+ 组件的完整解决方案。现有非托管 COM+ 组件中有一大部分是使用多种编程语言编写而成的,由于不可能测试所有可能的类型库(由支持 COM+ 的各种编译器生成),因此某些非托管 COM+ 组件不能使用 COM+ Web 服务正确发布。COM+ Web 服务的目的之一就是最大程度减少做出这种评估所需的时间和精力。只需将非托管 COM+ 组件发布为 COM+ Web 服务,开发人员就可以迅速判断是否可以将其用作 Web 服务。如果遇到问题,则可以通过若干替代方法来处理现有的非托管组件。这些替代方法包括编写托管或非托管的包装程序,这些包装程序提供的兼容接口可以发布为 Web 服务。多数情况下,编写这样的包装程序的工作量比重新编写整个组件要少得多。这就尽可能减少了将现有的应用程序应用为 XML Web Services 所需的开发和测试工作。

使用非托管(Visual Basic 6.0 或 Visual C++)服务器时,通常越早绑定托管客户端应用程序和 SOAP,越能更好地工作。在某些情况下,如果将生成的元数据用作后期绑定的跨计算机远程代理程序,它可能无法正常工作。

在通过 HTTP 使用 SOAP 格式化程序的情况下,仍然可以使用许多选项(取决于目标部署环境)。COM+ Web 服务为服务器和 CAO 客户端配置生成基于 XML 的远程配置文件。(WKO 激活的 URL 引用已嵌入生成的客户端代理,因此不需要配置文件。)COM+ Web 服务生成直观的功能性配置文件,可由用户自定义以满足任何通过 HTTP 的直接 SOAP 通信所不能满足的需求。可进行自定义的区域包括用户身份验证,如下例所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.runtime.remoting>
  <application>
   <service>
    <wellknown mode="SingleCall" type="SCTrans.SCTransSQL, SCTrans, 
      Version=0.0.0.0, Culture=neutral, 
      PublicKeyToken=9c6052078b454cee" 
      objectUri="SCTrans.SCTransSQL.soap" />
    <activated type="SCTrans.SCTransSQL, SCTrans" />
   </service>
  </application>
 </system.runtime.remoting>
 <identity impersonate="true" />
</configuration>

上例中添加的突出显示的行可以在激活 COM+ 组件(通过 SOAP 调用)时使用用户的身份标识。(默认情况下,IIS 虚拟根使用标准的 IIS 身份验证。)这样在使用 SOAP 时可以实现 COM+ 的分区(一种 COM+ Windows .NET Server 功能),从而可根据用户的身份标识来实际调用不同的组件。

另一个可以自定义的区域包括客户端激活对象的生存期管理,如下例所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.runtime.remoting>
  <application>
   <service>
    <wellknown mode="SingleCall" type="SCTrans.SCTransSQL, SCTrans, 
      Version=0.0.0.0, Culture=neutral, 
      PublicKeyToken=9c6052078b454cee" 
      objectUri="SCTrans.SCTransSQL.soap" />
    <activated type="SCTrans.SCTransSQL, SCTrans" />
   </service>
   <lifetime leaseTime="30S" renewOnCallTime="30S" />
  </application>
 </system.runtime.remoting>
</configuration>

在 web.config 文件中添加的突出显示的行,将此 IIS VRoot 中的客户端激活对象的生存期从 6 分钟更改为 30 秒。如果把 wellknown 元素的 SingleCall 属性更改为 Singleton,则激活行为会更改为将所有传入的方法调用都路由到一个组件,而不是原来的对于每个方法调用都激活一个新组件。

.NET Remoting(类似 .NET 框架的其余部分)支持垃圾回收,而不支持引用计数。这意味着在某些情况下,使用 COM+ Web 服务和 DCOM 时,非托管事务 COM+ 组件的行为方式将有所不同。对于通过 WKO 单一调用发布的事务方法,调用 SetComplete 或选择自动完成(通过选择组件方法属性页的“返回此方法时自动停用该对象”复选框)是极其重要的,这是因为组件在进行垃圾回收前不能被释放。使用 DCOM 时,引用计数通常会导致在释放组件时提交或放弃事务,即使此法则被忽略。移动到 COM+ Web 服务环境时,在垃圾回收环境中,事务超时之前这是不能保证的。如果调用 SetComplete 失败或将方法配置为自动完成失败,则证明其自身的间歇无法提交事务,因为组件被作为垃圾回收之前事务已超时。

设计时应注意的事项

在 COM+ Web 服务中,如果选择了 Uses SOAP 复选框(使用组件服务管理工具),将在 IIS 虚拟根上提供两种不同的激活模型:WKO 和 CAO。哪一种模型更好?用户应该使用哪一种呢?

WKO 单一调用激活模型看起来似乎颇为费事。每种方法调用都需要创建一个新组件,完成方法调用后,再将组件发送到内存回收器。但是,如果特别注重性能并且只能使用 WKO 处理业务时,缓冲的 ServicedComponents 或缓冲的非托管 C++ 组件可以大大缓解单一调用激活的过程。使用缓冲的组件时,WKO 激活将从缓冲池中检索对象,完成调用,然后将对象返回到缓冲池。此协议的无状态性质和缓冲池的使用提高了增加扩展性的可能。在不缓冲对象的 WKO 单一调用中,对象的生命期仅限于调用过程。

另一方面,CAO 提供了服务器上单一激活的性能优势,还可以与某个组件的单一实例继续进行通信。通过从客户端向服务器进行多方法调用可以避免激活的缺点。如果服务器组件(ServicedComponent 或非托管 C++ 组件)被缓冲,则将从缓冲池中检索对象,然后在完成方法调用时将对象返回到缓冲池。如果对象没有被缓冲,则对象生命期取决于 web.config 文件中指定的租用生命期,或由组件自身编程设置。生命期是很重要的,因为直到生命期到期时垃圾回收器才会为组件释放内存。在高容量的 CAO 配置中,这会影响开发人员的某些设计决定。

更进一步

如果您只是希望发布或使用应用了 COM+ Web 服务的 Web 服务,您可以到此为止。但是,如果您希望自定义、扩展或简单了解使用的流程,请继续阅读下面的内容。下面的信息不是使用此项功能所必需的,但是如果您希望手动扩展一些功能,这些信息可能会非常有用。COM+ Web 服务是一个简单的包装程序,通过由 .NET Remoting 提供的一套相当丰富的服务,开发人员或管理员可以轻松地对其进行扩展。

服务器 IIS 虚拟根

为使用此功能,并没有在 .NET Remoting 中添加隐藏挂钩,而是编写了 COM+ 代码以进行必要配置,将 COM+ 端点发布为 IIS 虚拟根。在服务器上,这包括向服务器创建物理目录作为虚拟根,以及生成 web.config 文件,以便通过 Remoting 来访问组件。如果是非托管组件(Visual C++ 或 Visual Basic 6.0),也会生成代理元数据,以便 Remoting 可以访问组件。如果 Windows XP 系统目录是 c:\windows,则服务器配置文件和生成的所有元数据都将存储在以下目录树中:

C:\windows\system32\com\SoapVRoots\vrootname\

当在服务器上发布 SOAP 端点时,以下生成的文件将被放入此目录中:

  • web.config: VRoot 的基本 Remoting 配置文件,包含许多选项,可供开发人员或系统管理员添加或编辑,以调整 Remoting 的性能和安全性。
  • default.disco: 如果您正在开发托管代码客户端,可与 Visual Studio .NET 一起使用此文件,来生成对已发布的 Web 服务的引用。如果您的业务伙伴希望在企业外联网上开发自己的客户端,这会特别有用。
  • default.aspx: 简单的 Microsoft ASP.NET 页,可以将每一组件发布为超链接。

上述所有文件都是默认生成的。如果您希望删除其中某些功能,只需编辑或删除相应的文件。(但是,如果删除了 web.config 文件,来自 IIS 虚拟根的所有 SOAP 发布都会停止。)

所有生成的元数据都被放入以下目录以及 GAC 中:

C:\windows\system32\com\SoapVRoots\vrootname\bin

在 .NET Remoting 中,bin 目录是一个很特殊的位置。当 HTTP 请求进入 IIS 时,将在此目录中搜索程序集,因此在许多情况下,bin 目录中的发布是唯一必要的步骤。但是,在发布 SOAP 端点时,生成的程序集也被放入 GAC,这是因为虚拟根的程序集解决方案的范围仅限于 bin 目录和 GAC。如果您的代码在同一台计算机上从一个虚拟根向另一个传递引用,除非程序集在 GAC 中,否则目标虚拟根中的引用解决方案将会失败。如果您正在使用所生成的用于非托管 Visual Basic 6.0 或 Visual C++ 组件的元数据,如果没有传递引用,则可以从 GAC 中删除所生成的程序集。

此版本的 .NET 框架需要特别注意的一点是:如果加载了程序集,并且使用 System.Reflection 来访问程序集文件,则文件将在内存中锁定,直到进程结束。动态生成 WSDL 以便生成代理时,将使用反射,因此对于将由客户端进程访问的活动 IIS 虚拟根来说,可以锁定程序集文件。这在运营环境中不会产生问题,但是对于经常更改组件的开发人员来说,应该牢记这一点。

如果您正在使用带有 COM+ Web 服务的 ServicedComponents,此时也需要将程序集放在 GAC 中,除非您最初将程序集放在了 bin 目录中,并且运行了针对该目录中程序集的 regsvcs.exe。如果已经加载 Microsoft .NET 框架 SDK,您可以使用 gacutil.exe 命令行实用程序,将 ServicedComponent 放入 GAC 中;如果安装了内置 .NET 框架的 Windows .NET Server,或者在 Windows XP 计算机上加载了可重新分发的 .NET 框架,可以使用 Microsoft .NET 框架配置用户界面(可从 Administrative Tools 菜单访问),将程序集添加到 GAC 中。

此外,使用 Windows XP 或 Windows .NET Server 时,请确保已安装并配置了 IIS,以提供 ASP.NET 应用程序服务。这些设置对于提供使用 SOAP 所必需的动态内容是必需的。

生成的代理程序集缓存

对于要通过 .NET Remoting 发布为 SOAP 端点的非托管 COM+ 组件,需要生成代理,使非托管组件可用于 .NET 框架。这可以通过编程执行与 tlbimp.exe(用于将非托管 COM+ 类型库转换为代理元数据程序集的 .NET 框架 SDK 工具)相同的步骤来完成。但是,要通过 SOAP 成功激活客户端,客户端和服务器计算机必须共享相同加强名称的签名元数据代理。因此,当生成用于非托管 COM+ 组件的托管代理程序集时,还会生成加强名称关键字,并用于签名代理程序集。

加强名称关键字只能生成一次,并且在非托管 COM+ 组件中没有加强名称关键字的概念。也就是说,如果多次生成代理,则可以创建不同的加强名称关键字。这会为同一非托管 COM+ 组件创建不同的托管标识,要避免这种情况,请将所有为非托管 COM+ 组件生成的代理程序集写入以下 SoapCache 目录中:

C:\windows\system32\com\SoapCache\componentdirectory\proxymetdata.dll

其中 componentdirectory 的格式应为:

ATLTrans.dll_40960_2001_6_27_15_4_16

目录名是根据文件名、文件大小以及上次编译的日期和时间创建的。此方案基于以下假设:如果重新编译非托管 COM+ 组件,则需要生成新的代理。而这又是基于以下假设:如果要对代码做出更改,只能在运营环境中重新编译代码。

由于存在 SoapCache 目录,所以如果在同一计算机的不同虚拟根发布了相同的非托管组件,而不是生成代理程序集,则位于缓存中的非托管组件将被重新使用。这是为了确保组件的加强名称签名(以及由此生成的标识)可以通过虚拟根共享。

如果将 SOAP 启用的非托管 COM+ 组件作为服务器应用程序导出,然后导入到其他服务器,缓存的代理元数据将被一起带走,因此不同的服务器可以共享相同的非托管程序集的同一托管标识。此外,如果用户要生成或编写并签名自己的代理,只需将元数据放入相应的缓存目录中,当服务器上发生 SOAP 发布时就会使用此元数据。这里应用的基本规则是,为避免不必要地扩散用于同一非托管组件的已签名的代理,如果缓存中存在可替代的文件则不生成程序集。

客户端配置

客户端的配置工作也是必需的,最简单的情况(至少从用户的工作量来说)就是本文给出的第一个程序示例:

set SoapObj = 
   GetObject("soap:wsdl=http://www.xmethods.net/sd
                   /TemperatureService.wsdl")
WScript.Echo "Fairbanks  气温 = " & SoapObj.getTemp("99707")

当处理 WSDL 名字对象时,将会引发以下步骤:

  1. 进行检查,查看是否存在以前为此 URL 生成的代理。如果存在,则再次使用。(跳到步骤 4。)
  2. 如果不存在,则从 URL 检索 WSDL 并生成 C# 代理程序。这实质上与 soapsuds.exe 命令行实用程序(.NET 框架 SDK 所附带的)使用的逻辑相同。
  3. C# 程序被编译为 DLL 并以与 URL 相匹配的名称命名(非法字符转换为文件名中可接受的字符)。
  4. 然后,生成的代理用于通过 .NET Remoting (WKO) 与 WSDL 中指定的远程服务器通信。

这些代理生成并保存在以下文件夹中:

C:\windows\system32\com\SoapAssembly\

在客户端激活的情况中,客户端代理导入客户端计算机上所必需的已导出的 COM+ 应用程序。此应用程序的导出/导入将从服务器带来客户端激活所必需的已签名的元数据程序集。导入过程还生成配置文件,并放入 SoapAssembly 目录中。通常客户端配置文件采用以下格式:

<configuration>
 <system.runtime.remoting>
  <application>
   <client url="http://MyServer/VB6Soap">
    <activated type="VB6SoapSoapLib.CalcClass, VB6SoapSoapLib"/>
   </client>
  </application>
 </system.runtime.remoting>
</configuration>

COM+ Web 服务在激活组件前读取此配置文件,这样便可以通过修改或替换此配置文件,在客户端计算机上潜在更改激活模型。

一切才刚刚开始

COM+ Web 服务的设计目的是简化结合 .NET Remoting 和 COM+ 服务(Windows XP 和 Windows .NET Server 系列均包含此服务)的过程。它只是为了简化常见的任务,并非包含所有的选项或涵盖用户可能遇到的各种情况。与使用向导在 Visual Studio .NET 中创建程序类似,某些高级的任务留给用户自行解决。为了使用户可以扩展,生成的项目很少被完全删除。此外,XML 类用于编辑生成的配置文件,如果已经存在配置文件,则会在该文件中添加或删除节点,以反映来自组件服务管理工具或 Microsoft COM+ 管理 SDK 的更改。COM+ Web 服务的设计使用户可以轻松地扩展或自定义已经生成的内容。

总之,COM+ Web 服务为现有的 Visual Basic 和 Visual C++ COM+ 组件,以及在 Visual Basic .NET 和 C# 中编写的新托管的 ServicedComponents,提供了一条实现 XML Web Services 和 SOAP 的简单途径。