约翰·诺斯和乔纳森·霍金斯
Microsoft Corporation
2001 年 11 月
总结:COM+ Web Services 添加了与 Microsoft .NET 远程处理集成的功能,以便通过 SOAP for COM+ 组件提供 XML Web 服务发布的检查盒激活。 本文档包含几个示例和演练,涵盖了在 Microsoft Windows Server 2003 和 Microsoft Windows XP Professional 上作为 XML Web 服务发布的托管和非托管 COM+ 组件的基本互操作性、配置和部署。 ) (26 个打印页
目录
简介
简单的 Well-Known 对象 (WKO) 示例
简单Client-Activated对象 (CAO) 示例
事务组件示例
一个开始,而不是一个结束...
简介
COM+ Web Services 添加了与 Microsoft .NET 远程处理集成的功能,以便通过 SOAP for COM+ 组件提供 XML Web 服务发布的检查盒激活。 本文档包含几个示例和演练,涵盖了在 Microsoft Windows Server 2003 和 Microsoft® Windows® XP Professional 上作为 XML Web 服务发布的托管和非托管 COM+ 组件的基本互操作性、配置和部署。 还提供了运行 Windows XP 的客户端可访问远程服务器上的 XML Web 服务的新功能示例。
这些功能的目标是在开发人员开始使用 .NET 远程处理和托管代码来补充现有的非托管 COM+ 服务器和客户端代码时,简化迁移过程。 在.NET Framework的 beta 阶段,用户经常遇到有关如何配置 .NET 远程处理以执行简单的跨计算机激活的问题。 COM+ Web 服务解决方案是自动将服务器 (Microsoft Windows Server 2003) 和客户端 (Microsoft Windows XP Professional) 计算机配置为使用 .NET 远程处理来提供 SOAP 作为 DCOM 的替代方法。
今年最大的两个软件版本将是 Microsoft Windows XP 和 Microsoft .NET Framework。 每个产品的目标是简化和扩展软件开发人员的功能,因此,利用这两种产品并提供集成、易于使用的解决方案来整合这两种产品的优势是自然而然的一步。 COM+ Web Services 提供了一种将 COM+ 组件发布为 XML Web 服务的简单方法,还提供了从客户端计算机访问 XML Web 服务的新集成功能。 从以下 Microsoft Visual Basic Scripting Edition (VBScript) 示例中可以看到这种易用性,该示例确定阿拉斯加费尔班克斯的当前温度。 此示例在安装了 .NET Framework) 的 Windows XP (或 Windows Server 2003 (上运行,) 需要.NET Framework候选版本或更高版本版本:
set SoapObj = GetObject
("soap:wsdl=http://www.xmethods.net/sd/TemperatureService.wsdl")
WScript.Echo "Fairbanks Temperature = " & SoapObj.getTemp("99707")
在前面的示例中,服务器是在 Linux 上运行的 Apache SOAP 服务器,但它可以是具有标准 Web 服务描述语言 (WSDL) 说明的任何 SOAP 版本 1.1 服务器。
注意如果收到“找不到服务器”错误,则需要在 控制面板 的 Internet 选项中手动配置防火墙设置。
使用 SOAP 作为计算机之间的通信协议的优点之一是,它增加了可以互操作的计算机种类。 .NET 远程处理具有以下两种基本操作模式:
- 已知对象 (WKO) WKO 是 SOAP 版本 1.1 支持的最常见 XML Web Services 模型。 它允许与运行符合 SOAP 版本 1.1 的堆栈的其他计算机进行互操作性。 服务器和客户端的范围从运行 Apache SOAP 的非 Windows 服务器到运行 pocketSOAP 的 Pocket 电脑,以及基于 Windows 的服务器和客户端。 唯一的要求是服务器必须具有 WSDL 版本 1.1 兼容说明,以便可以生成适当的代理。 此代理在运行时生成,首次使用 WSDL 名字对象时无需用户干预。
- 客户端激活的对象 (CAO) 与典型的 XML Web 服务模型相比,CAO 提供了更丰富的开发环境,包括更像 DCOM 模型的有状态持久连接,但它需要两端的.NET Framework版本。
使用 COM+ Web 服务时,WKO 和 CAO 激活模型均可用,并发布所有服务器应用程序以提供 WKO 和 CAO 终结点。 通过使用激活模型、XML Web 服务和 .NET 远程处理的组合,开发人员可以轻松地混合和匹配托管和非托管客户端与服务器。 下表显示了每个激活模型支持的方案示例。
表 1. WKO 模型支持的方案
| WKO 客户端 | WKO 服务器 |
|---|---|
| Visual Basic 6.0 或非托管 C++ | Visual Basic 6.0 或非托管 C++ |
| Visual Basic 6.0 或非托管 C++ | Visual Basic .NET 或 C# |
| Visual Basic 6.0 或非托管 C++ | WSDL) 中所述的 SOAP v1.1 ( |
| Visual Basic 6.0 或非托管 C++ | Microsoft SOAP (ATL Server、SOAP TK) |
| C# 或 Visual Basic .NET | WSDL) 中所述的 SOAP v1.1 ( |
| C# 或 Visual Basic .NET | Visual Basic 6.0 或非托管 C++ |
| C# 或 Visual Basic .NET | Visual Basic .NET 或 C# |
| C# 或 Visual Basic .NET | Microsoft SOAP (ATL Server、SOAP TK) |
| Microsoft SOAP Toolkit V2.0 | Visual Basic 6.0 或非托管 C++ |
| Microsoft SOAP Toolkit V2.0 | C# 或 Visual Basic .NET |
| SOAP v1.1 | Visual Basic 6.0 或非托管 C++ |
| SOAP v1.1 | C# 或 Visual Basic .NET |
表 2. CAO 模型支持的方案
| CAO 客户端 | CAO 服务器 |
|---|---|
| C# 或 Visual Basic .NET (早期绑定) | Visual Basic 6.0 或非托管 C++ |
| Visual Basic 6.0 或非托管 C++ | Visual Basic 6.0 或非托管 C++ |
| Visual Basic 6.0 或非托管 C++ | C# 或 Visual Basic .NET |
| C# 或 Visual Basic .NET | C# 或 Visual Basic .NET |
此新 COM+ Web 服务的目标受众包括以下用户:
- 当前 COM+ 客户拥有 Microsoft® Visual Basic® 6.0 或非托管Microsoft Visual C++ ® COM+ 应用程序,这些应用程序需要通过防火墙进行某些激活。 (使用 SOAP 不排除通过 DCOM 访问服务器上的同一组件 -- 协议的选择留给客户端计算机。) 对于这些客户,使用客户端代理导出和 CAO 模型应该不需要在客户端或服务器应用程序中进行任何更改,以便使用 SOAP 而不是 DCOM。 只需在服务器应用程序上启用 SOAP,将其导出为客户端代理,并在要用作 SOAP 客户端的 Windows XP 计算机上安装代理。
- 公司完全迁移到 Windows XP 和 Windows Server 2003 上的托管代码。 COM+ Web 服务有助于在连接的两端设置远程处理终结点。
- 需要混合和匹配上述两种方案中的各种服务的开发人员,以及使用非托管服务器组件编写托管服务器组件或托管客户端应用程序的开发人员。 在后一种情况下,开发人员可以使用 COM+ Web 服务,在将旧版非托管组件替换为托管代码之前,最大限度地利用这些组件。
简单的 Well-Known 对象 (WKO) 示例
除了为 Linux 和 Apache 提供 SOAP 支持外,将 COM+ Web 服务与其他 Microsoft 产品(如 ATL 服务器 Web 服务)结合使用也非常简单。 只需使用 Microsoft Visual Studio® .NET 在服务器上生成、编译和部署默认 ATL Web 服务。 访问它的客户端代码如下所示, (将 MyServer 替换为托管 ATL Server 应用程序的 Web 服务器的名称,并将 JALTServer 替换为 ATL 服务器 DLL) 的名称:
mon="soap:wsdl=http://MyServer/JALTServer/JALTServer.dll?
Handler=GenJALTServerWSDL"
set c = GetObject(mon)
WScript.Echo c.HelloWorld("COM+ Web Services")
前面的示例简单演示了 Microsoft Windows XP 和 Microsoft Windows Server 2003 系列随附的新 SOAP 名字对象之一。 若要使用名字对象来使用 ASP.NET Web 服务,Web 服务必须具有 System.Web.Services.Protocols.SoapRpcService 类属性。
数据发布
如果要提供数据而不是使用数据,该怎么办? 只需选择检查框,然后输入 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 项目属性”页的“ 常规 ”选项卡上,设置“ 无人参与执行 ”和 “在内存中保留”,然后在“ 组件 ”选项卡上,选择“ 远程服务器文件”。 使用 Visual Basic 开发环境生成此 DLL。
创建 Visual Basic 应用程序后,需要将其注册为 COM+ 应用程序。 启动组件服务管理工具,并在 Windows XP 上创建 COM+ 应用程序。 (在此示例中,应用程序名为 VB6Soap.) 导入作为组件创建的 DLL。 然后导航到“COM+ 应用程序属性”页的“ 激活 ”选项卡,选择“ 使用 SOAP”,输入 SOAP Vroot(如 VB6Soap),然后单击“ 确定”,如图 1 所示。
.gif)
图 1. VB6Soap COM+ 应用程序属性页
该应用程序现已发布为 XML Web 服务,可以使用 SOAP 激活。 使用 Internet Explorer,可以导航到 https://localhost/VB6Soap/default.aspx,可在 aspx 页面上找到指向组件生成的 WSDL 的超链接。 以下 VBScript 将激活组件:
set c = GetObject
("soap:wsdl=https://localhost/VB6Soap/VB6Soap.Calc.soap?WSDL")
for i = 1 to 10
WScript.Echo i & " " & c.Add(i,i) & " " & Time
next
如果在上述脚本中将服务器名称替换为 localhost,则它同样适用于远程客户端计算机。 本示例 VB6Soap.Calc.soap) 中的页面引用 (是组件 ProgID,后跟 .soap 后缀。
若要通过作为 Windows XP Professional 的一部分附带且不使用 .NET 远程处理的 SOAP 工具包访问同一终结点,请运行以下 VBScript:
set c = CreateObject("MSSOAP.SOAPClient")
c.mssoapinit("https://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 继承。 下面是简单托管组件的托管代码示例, (这些 ServicedComponent 示例将适用于 .NET Server Beta 3 或更高版本版本,以及.NET Framework候选版本或更高版本。SoapVRoot 功能将在将来的 Service Pack) 中引入 Windows XP Pro:
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 Framework 用户界面将此编译程序集放入全局程序集缓存 (GAC) 中。 若要通过 WSDL 访问此 SOAP 终结点,请使用以下 VBScript:
set c = GetObject
("soap:wsdl=https://localhost/CSSoap/CSSoap.Calc.soap?WSDL")
for i = 1 to 10
WScript.Echo i & " " & c.Add(i,i) & " " & Time
next
作为 SOAP 互操作性的简单示例,SOAP 工具包随 Windows XP Professional 附带,以下 VBScript 将访问 COM+ SOAP 终结点,即使运行 Windows XP 的客户端计算机上未安装.NET Framework:
set c = CreateObject("MSSOAP.SOAPClient")
c.mssoapinit("https://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=https://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 Services 允许将部分应用程序转换为托管代码,而无需立即对现有应用程序进行全面重写。
简单Client-Activated对象 (CAO) 示例
服务器上的 COM+ Web 服务发布将发布为每个组件的 WKO 和 CAO,因此无需进行额外的服务器配置。 服务器上需要执行的唯一步骤是在选择“使用 SOAP 检查”框 (“COM+ 应用程序属性”页的“激活”选项卡上) 并在“SOAP VRoot”文本框中输入值后,将 COM+ 应用程序导出为代理应用程序。 下图显示了导出代理应用程序所需的步骤:
在组件服务管理工具中右键单击 VB6Soap COM+ 应用程序,然后选择 “导出”,如图 2 所示。
.gif)
图 2. 组件服务管理工具
在图 3 所示的 COM+ 应用程序导出向导 中,输入代理.msi文件的位置和名称。
.gif)
图 3. COM+ 应用程序导出向导
在单独的客户端计算机上,将代理.msi文件安装为预生成的 COM+ 应用程序。
在客户端计算机上安装时,代理将被正确配置为通过 SOAP 访问正确的服务器和虚拟根。 对于客户端激活,可以 (CoCreateInstance、 CreateObject 等) 使用正常的非托管 COM+ 激活,而不是使用 WSDL 名字对象。 在服务器上创建上述 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# 示例所示 (这些 ServicedComponent 示例适用于 .NET Server Beta 3 或更高版本版本,以及.NET Framework候选版本或更高版本。SoapVRoot 功能将在将来的 Service Pack) 中引入 Windows XP Pro:
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 语句中的程序集引用对其进行编译。 如果使用 SDK) ,则应使用 gacutil.exe (或通过 .NET Framework 用户界面(如果要在服务器上部署)将程序集放入 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 示例,其中显示了此操作的基础知识, (这些 ServicedComponent 示例将适用于 .NET Server Beta 3 或更高版本,以及.NET Framework候选版本或更高版本。SoapVRoot 功能将在将来的 Service Pack) 中引入 Windows XP Pro:
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# 程序有两个类: Child 和 Parent。 如果运行以下 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:
'Create two objects directly
set c1=CreateObject("RefPass.Child")
set c2=CreateObject("RefPass.Child")
'Set the name of the first object, and call it several times
'to increment the object internal counter
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
'Set the name of the first object, and call it several times
'to increment the object internal counter
c2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
'Create a parent object
set p=CreateObject("RefPass.Parent")
'Pass the child objects to the parent and call it from the 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)
'Now call the child object stored internally in the parent
dim c9
WScript.Echo p.CountUp(c9)
'Get the object back from the parent and call it directly
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 远程处理激活模型中不同。
除了使用 CreateObject 调用 CAO 激活外,还通过 COM+ 提供了一个名字对象,该名字对象提供 CAO 激活而不是 WKO(类型名和程序集名字对象)。 以下脚本:
'Create two objects directly
set c1=GetObject("soap:typename=RefPass.Child,assembly=RefPass")
set c2=GetObject("soap:typename=RefPass.Child,assembly=RefPass")
'Set the name of the first object, and call it several times
'to increment the object internal counter
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
'Set the name of the second object, and call it several times
'to increment the object internal counter
c2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
'Create a parent object
set p=GetObject("soap:typename=RefPass.Parent,assembly=RefPass")
'Pass the child objects to the parent and call it from the 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)
'Now call the child object stored internally in the parent
dim c9
WScript.Echo p.CountUp(c9)
'Get the object back from the parent and call it directly
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) 示例相同。 由于 SOAP 代理应用程序的正常 COM+ 激活路径被截获, 因此 CoCreateInstance、 CreateInstance 和其他传统的 COM+ 激活方法可用于使用 COM+ Web Services 调用客户端激活的对象。
程序集和 typename 名字对象也可用于从托管代码客户端获取预配置的客户端激活远程处理,如以下示例所示:
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 远程处理的目标之一是提供一个丰富的分布式环境,开发人员可以在其中混合和匹配序列化协议 (格式化程序) 网络协议 (通道) 。 .NET Framework版本 1.0 中的 COM+ Web 服务仅支持一个格式化程序 (SOAP) 和一个通道 (HTTP) 。 这并不意味着其他通道和格式化程序将无法与 ServicedComponents 或 COM+ 一起使用,而是没有自动配置为这些备用通道和格式化程序提供客户端和服务器终结点。
大量 COM+ 组件已使用多种语言编写。 如果所有这些组件都可以使用 COM+ Web 服务作为 Web 服务启用,那就非常了不起了。 与 .NET Framework 版本 1.0 一样,并非所有现有 COM 组件都适用于 COM+ Web 服务。 虽然大多数具有类型库的现有组件应该运行良好,但此版本不支持某些组件,例如Windows 脚本组件 (WSC) 组件。 接口具有多个继承级别或依赖于多个类型库的一些复杂类型库可能无法正常工作。 此外,由于类型库转换的限制,只有类型库中的默认接口可用作 Web 服务。
对于每个现有的非托管 COM+ 组件,COM+ Web 服务并不是一个完整的解决方案。 存在大量以各种编程语言编写的现有非托管 COM+ 组件。 无法测试每个支持 COM+ 的编译器生成的每个可能的类型库,因此某些非托管 COM+ 组件无法使用 COM+ Web 服务正确发布。 COM+ Web 服务的目标之一是最大程度地减少进行此评估所需的时间和工作量。 只需将非托管 COM+ 组件发布为 COM+ Web 服务,开发人员即可快速确定作为 Web 服务的预期用途是否存在任何问题。 如果遇到问题,可通过多种替代方法与现有的非托管组件一起使用。 这些替代方法包括编写托管或非托管包装器,以提供可发布为 Web 服务的兼容接口。 在大多数情况下,编写此类包装器比重写整个组件要少得多。 这最大限度地减少了将应用程序作为 XML Web 服务投入生产环境的开发和测试工作量。
使用非托管 (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>
在通过 SOAP 调用时,添加上面突出显示的行会导致在激活 COM+ 组件时使用用户标识。 (默认情况下,IIS 虚拟根使用标准 IIS 身份验证。) 这允许 COM+ 分区 (COM+ Windows Server 2003 功能) 与 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>
<lifetime leaseTime="30S" renewOnCallTime="30S" />
</application>
</system.runtime.remoting>
</configuration>
将突出显示的行添加到 web.config 文件会将此 IIS VRoot 中客户端激活对象的生存期从 6 分钟更改为 30 秒。 将 wellknown 元素的 SingleCall 属性更改为 Singleton 将更改激活行为,以将所有传入的方法调用路由到组件的单个实例,而不是在每个方法调用上激活新组件。
.NET 远程处理 (与.NET Framework) 的其余部分一样支持垃圾回收,而不是引用计数。 这意味着,在少数情况下,非托管事务性 COM+ 组件使用 COM+ Web 服务的行为将不同于使用 DCOM。 对于通过 WKO 单次调用发布的事务性方法,必须调用 SetComplete 或自动完成 (,方法是在) 选择组件方法属性页中的“在此方法返回检查时自动停用此对象”框。 这是必需的,因为在组件被垃圾回收之前,组件不会被释放。 使用 DCOM 时,引用计数通常会导致在释放组件时提交或中止事务,即使忽略此规则也是如此。 移动到 COM+ Web 服务环境时,在事务在垃圾回收环境中超时之前,无法保证这一点。 调用 SetComplete 或将 方法配置为自动完成失败将表现为提交事务的间歇性失败,因为它们将在组件被垃圾回收之前超时。
设计注意事项
在 COM+ Web 服务中,使用组件服务管理工具) (选择“使用 SOAP 检查”框时,会在 IIS 虚拟根上公开两个不同的激活模型:WKO 和 CAO。 哪一个更好,应该使用哪一个?
WKO 单调用激活模型听起来非常昂贵。 每次调用方法时,都会创建一个新组件,进行方法调用,并将组件发送到垃圾回收器。 但是,在性能至关重要且 WKO 是当前业务流程的正确模型的领域,共用 ServicedComponents 或共用非托管 C++ 组件将缓解单个调用激活的大部分处理。 使用共用组件时,当 WKO 激活传入时,将从池中检索对象,进行调用,并将对象返回到池。 此协议的无状态性质和池的使用提高了可伸缩性的可能性。 在对象未共用的 WKO 单次调用中,对象的生存期仅在调用期间。
另一方面,CAO 提供在服务器上进行单次激活和继续与组件的单个实例通信的性能优势。 在从客户端到服务器的多个方法调用时,可以避免激活开销。 如果服务器组件 (ServicedComponent 或非托管 C++ 组件) 已入池,则会从池中检索对象,然后在方法调用完成时返回到池。 如果对象未入池,则对象生存期依赖于web.config文件中指定的租用生存期,或者由组件本身以编程方式设置。 生存期很重要,因为垃圾回收器在生存期过期之前不会释放组件的内存。 在大容量 CAO 配置中,这将涉及开发人员的一些设计决策。
在掩护下
如果只想使用 COM+ Web 服务发布或使用 Web 服务,则可以在此处停止阅读。 但是,如果要自定义、扩展或只是了解使用的过程,请继续阅读。 使用此功能不需要以下信息,但如果要手动扩展这些功能,这些信息可能很有用。 COM+ Web 服务是一个简单的包装器,通过 .NET 远程处理提供的一组非常丰富的服务,开发人员或管理员可以轻松扩展。
服务器 IIS 虚拟根
若要使此功能正常工作,未向 .NET 远程处理添加任何隐藏挂钩。 而是编写 COM+ 代码来执行将 COM+ 终结点发布为 IIS 虚拟根所需的配置。 在服务器上,这涉及到创建一个物理目录作为虚拟根目录,并生成一个web.config文件,使组件可通过远程处理进行访问。 如果组件是非托管的 (Visual C++ 或 Visual Basic 6.0) ,则还会生成代理元数据,以便远程处理可以访问组件。 如果 Windows XP 系统目录为 c:\windows,则服务器配置文件和任何生成的元数据都驻留在以下目录树中:
C:\windows\system32\com\SoapVRoots\vrootname\
在服务器上发布 SOAP 终结点时,以下生成的文件将放入此目录中:
- web.config vroot 的基本远程处理配置文件,其中包含许多选项,开发人员或系统管理员可以添加或编辑这些选项来优化远程处理性能和安全性。
- default.disco 一个可用于 Visual Studio .NET 的文件,用于生成对已发布 Web 服务的引用(如果你正在开发托管代码客户端)。 这在合作伙伴想要开发自己的客户端的 Extranet 案例中尤其有用。
- default.aspx 一个简单的 Microsoft ASP.NET 页面,将每个组件发布为超链接。
上述所有文件默认生成。 如果要删除其中一些功能,只需编辑或删除相应的文件。 (如果删除web.config文件,则从 IIS 虚拟根目录发布的所有 SOAP 发布都将停止。)
所有生成的元数据都放置在以下目录和 GAC 中:
C:\windows\system32\com\SoapVRoots\vrootname\bin
在 .NET 远程处理中,bin 目录是一个特殊位置。 当 HTTP 请求传入 IIS 时,将在此目录中搜索程序集,因此在许多情况下,bin 目录中的发布是唯一必需的步骤。 但是,发布 SOAP 终结点时,生成的程序集也会放在 GAC 中。 这是因为虚拟根的程序集解析范围仅限于 bin 目录和 GAC。 如果代码将对对象的引用从一个虚拟根传递到同一计算机上的另一个,则目标虚拟根中的引用解析将失败,除非程序集位于 GAC 中。 如果为非托管 Visual Basic 6.0 或 Visual C++ 组件使用生成的元数据,则可以从 GAC 中删除生成的程序集(如果未传递引用)。
有关此版本中.NET Framework的一个重要注意事项是,如果加载了程序集并使用 System.Reflection 访问程序集文件,则文件将锁定在内存中,直到进程终止。 当动态生成 WSDL 以生成代理时,将使用反射,因此,对于客户端进程正在访问的活动 IIS 虚拟根,程序集文件很有可能被锁定。 这不太可能在生产环境中造成问题,但对于经常更改组件的开发人员来说,应该牢记这一点。
如果将 ServicedComponents 与 COM+ Web Services 配合使用,则还要求程序集位于 GAC 中,除非最初将程序集放在 bin 目录中,并在该目录中的程序集上运行regsvcs.exe。 如果已加载 Microsoft .NET Framework SDK,则可以使用 gacutil.exe 命令行实用工具将 ServicedComponent 放入 GAC 中。 如果 Windows Server 2003 具有内置.NET Framework或已在 Windows XP 计算机上加载了.NET Framework可再发行组件,则可以使用可从“管理工具”菜单访问的 Microsoft .NET Framework 配置用户界面将程序集添加到 GAC。
此外,在使用 Windows XP 或 Windows Server 2003 时,请确保安装并配置 IIS 以提供 ASP.NET 应用程序服务。 这些设置是提供使用 SOAP 所需的动态内容所必需的。
生成的代理程序集缓存
若要通过 .NET 远程处理发布为 SOAP 终结点的非托管 COM+ 组件,需要生成代理,使非托管组件可供.NET Framework使用。 这是通过以编程方式执行与 tlbimp.exe 相同的步骤来完成的,.NET Framework SDK 工具用于将非托管 COM+ 类型库转换为代理元数据程序集。 但是,若要通过 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 Temperature = " & SoapObj.getTemp("99707")
处理 WSDL 名字对象时,将执行以下步骤:
- 进行检查以查看之前是否已为此 URL 生成代理。 如果是这样,将再次使用它。 (跳到步骤 4.)
- 如果检查失败,将从 URL 检索 WSDL,并使用与 .NET Framework SDK 附带的 soapsuds.exe 命令行实用工具使用的逻辑基本相同的逻辑生成 C# 代理程序。
- C# 程序编译为 DLL,并提供与 URL (匹配的名称,其中非法字符转换为文件名) 中可接受的字符。
- 然后,生成的代理用于通过 .NET 远程处理 (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 Services 在激活组件之前读取此配置文件,因此可以通过修改或替换此配置文件在客户端计算机上更改激活模型。
开始,而不是结束...
COM+ Web 服务旨在简化将 .NET 远程处理与 Windows XP 和 Windows Server 2003 系列中包含的 COM+ 服务相结合的一些步骤。 它不是为了包括每个选项或涵盖任何用户应变,而是为了简化常见任务。 以类似于使用向导在 Visual Studio .NET 中创建程序的方式,将一些高级任务留给用户。 为了允许用户扩展性,生成的项很少完全删除。 此外,XML 类用于编辑生成的配置文件,如果已有配置文件,则会从其中添加和删除节点,以响应来自组件服务管理工具或 Microsoft COM+ 管理 SDK 的更改。 COM+ Web 服务旨在让用户轻松扩展或自定义已生成的内容。
总之,COM+ Web Services 为现有 Visual Basic 和 Visual C++ COM+ 组件以及用 Visual Basic .NET 和 C # 编写的新的 托管服务组件提供了 XML Web 服务和 SOAP 的简单介绍。