将 DCOM 远程处理功能引入 Windows CE 和 .NET CF2.0






4.93/5 (10投票s)
本文演示了如何在 Windows CE 5.0 上使用 DCOM。我们将添加完整的 DCOM 丰富错误信息,并在 Windows XP .NET 2.0 客户端和 Windows CE DCOM 服务器之间实现 DCOM 接口。通过此代码,可以实现 DCOM 互操作的 .NET 远程处理类功能。
引言
设想您正在构建一个实时控制应用程序,例如工厂机器人,并且希望从 .NET 客户端控制您的机器人。客户端可以托管在 Windows XP/2000 计算机上,或者托管在实时控制机器人本身的 Windows CE 驱动的单板计算机上。您希望从机器人接收事件以在不轮询设备的情况下指示某些状态。那么,本文将向您展示如何实现这一点。
解决此问题的一种可能方法是在 Windows CE 设备上实现 Web 服务,但这项技术的缺点是您无法直接从设备(服务器)向客户端发送异步事件(只要 WS 事件不支持)。
另一个当前广为人知的技术是 .NET Remoting。但是,此技术目前在 Windows CE 5.0 和 .NET CF 2.0 中不可用。
如果您熟悉 COM,可以考虑 DCOM。由于 .NET CF 2.0 中提供了 COM 互操作,并且 Windows CE 5.0 支持 DCOM,因此这可能是一个可行的解决方案。诚然,服务器需要实现为 COM 服务器,但所有客户端——通常是用户界面——都可以用 .NET 编写。另一个需要知道的事实是,在编写实时应用程序时,由于其不可预测的垃圾回收器,不能使用 .NET,这使我们回到了原生 C++。如果设计得当,COM 服务器可以执行实时任务,同时向外部世界公开 COM 接口。
因此,COM 服务器将用 C++ 编写,客户端用 C# 编写。作为奖励,我们将对运行在 Windows XP 和 .NET Framework 上的远程客户端以及运行在 Windows CE 和 .NET Compact Framework 上的本地客户端使用相同的 C# 源代码,展示 .NET 在多个操作系统平台上运行的真正强大功能。任何在 Windows CE 下使用过 DCOM 的人都知道,COM 丰富错误信息无法从服务器正确地传播回客户端。示例程序还将实现此缺失的功能。
为 DCOM 准备您的 Windows CE 映像
不幸的是,Pocket PC 2003 和 2005 不支持 DCOM。了解这一点后,我们将需要通过 Platform Builder 5.0 创建自己的 Windows CE 映像,该工具用于创建、配置和构建您自己的 Windows CE 5.0 映像。
首先,从 Microsoft 网站下载 Platform Builder 5.0 的 .NET CF 2.0 CEC 安装程序,并安装它,以便在 Platform Builder 中的目录中可用。我们需要 Compact Framework 2.0,因为它提供了早期版本的 Compact Framework 所没有的 COM 互操作功能。创建一个支持完整 DCOM 和 .NET CF 2.0 的 Windows CE 映像。如何创建 Windows CE 映像在其他地方已有最佳解释,因此我将在此处不详细介绍。
由于我们将使用 Visual Studio 2005 来创建和调试我们的代码,因此建议将用于使用 Visual Studio 2005 调试应用程序的必要可执行文件和 DLL 也添加到您的映像中。有关如何执行此操作,请查阅在线帮助。
添加到映像中的其他有用应用程序包括
- DCOMCnfg.exe 用于 Windows CE 配置您的 DCOM 设置。
- RegsvrCE.exe 用于 Windows CE 注册您的 COM DLL。
- NTLMUser.exe 用于 Windows CE 在 Windows CE 设备上创建本地 NTLM 帐户。
所有这些工具也包含在下载包的源代码中。
注意:本文中的所有 Windows CE 示例项目都将引用 DSM52 SDK。这是我创建的映像名称,您需要在创建/使用 Visual Studio 2005 中的任何智能设备项目之前安装它。如果您给 SDK 起其他名称,则需要在 vcproj 文件中将所有“DSM52”引用替换为您的 SDK 名称,以便打开和加载示例代码项目。
设置 COM 服务器
Windows CE (5.0 及更早版本) 不支持自动化封送器,有时也称为类型库封送器。因此,我们必须创建自己的代理/存根封送代码来配合我们的 COM 服务器和客户端,以确保我们的 COM 调用在进程边界之间正确封送。但是,如果我们坚持使用 OLE Automation 兼容的数据类型在 COM 方法中,则无需在 Windows XP/2000 客户端端提供代理/存根 DLL,因为自动化封送器始终存在于那里(作为 ole32.dll 和 oleaut32.dll 的一部分)。如果您仍需要使用其他数据类型,则有义务将代理/存根代码也编译到 Windows XP/2000 上并在此处注册。
出于同样的原因,我们必须修改 IDL 文件中的 LIBRARY
块中的事件接收器 dispinterface(由 Visual Studio ATL COM 项目向导生成用于事件通知),并将其更改为 LIBRARY
块外部的接口部分。对于我们 IDL 文件 LIBRARY
部分之外定义的所有接口,MIDL 编译器将创建代理/存根代码,需要将其添加到您的代理/存根 DLL。完成此操作后,您的事件回传给客户端的封送代码也将在代理/存根 DLL 中创建。请注意,此接口被标记为 dual 并派生自 IDispatch
,模拟 dispinterface 功能。
#ifdef UNDER_CE /* Windows CE does not support Type Library Marshaling, so we need to make it a custom interface that can be Proxy/Stub Marshalled */ [ object, uuid(8DC5953F-FBE6-4CF5-9FB5-4FF8A7B15530), dual, helpstring("_IGateEvents Interface"), pointer_default(unique) ] interface _IGateEvents : IDispatch { [id(1), helpstring("method Opened")] HRESULT Opened([in] VARIANT Destination); }; #endif //UNDER_CE ... LIBRARY StarGateLib { ... #ifndef UNDER_CE [ uuid(8DC5953F-FBE6-4CF5-9FB5-4FF8A7B15530), helpstring("_IGateEvents Interface") ] dispinterface _IGateEvents { properties: methods: [id(1), helpstring("method Opened")] HRESULT Opened([in] BSTR Destination); }; #endif //UNDER_CE ... coclass Gate { ... [default, source] dispinterface _IGateEvents; }; };
现在我们讨论封送,我想解决在 Windows CE 下使用 COM 的另一个问题。如果 COM 方法调用在执行过程中发生问题,通常的做法是返回错误代码 (HRESULT
)。为了帮助用户解决问题,最好也提供丰富的错误描述。目前,Windows CE 不会在进程边界上传播此丰富错误信息(通过 GetErrorInfo()
和 SetErrorInfo()
API)。稍后在本文中,我们将展示如何为您的代码添加此缺失的功能。作为确定 COM 服务器是否实现了丰富错误信息并提供了丰富错误信息的一部分,还需要封送 ISupportErrorInfo
接口。此接口的封送代码在 Windows CE 中也不可用。因此,代理/存根封送代码是手动生成的,并且也添加到代理/存根 DLL 中。ISupportErrorInfo
接口从 oaidl.idl 复制。
图 1. DCOM 封送
现在让我们创建 COM 服务器
注意:在使用 Visual Studio 2005 创建我们的智能设备示例代码之前,需要安装我们自己创建的映像的 SDK(包含 DCOM 支持的平台),以便 Visual Studio 2005 可以选择它来用于我们自己的项目。
我们使用 Visual Studio 2005 创建了一个 C++ ATL 智能设备项目。服务器将是一个 ATL COM EXE 服务器。我创建的示例程序灵感来自著名的电视剧《星际之门》,它允许您拨入我们银河系甚至更远的星球,前提是您知道如何对付坏人。您是否曾想遇到 Thor 并访问 Atlantis?快来看看。通过向导,我们可以为我们的接口添加一些方法,并在我们的 COM 对象中实现它们。
请注意,我们确实添加了对 ISupportErrorInfo
和连接点的支持,并且我们使用了 free-threading 模型。这是创建 COM 对象时的正常过程。我为向导生成的代码添加的一点是,COM 事件不是从调用“传入”接口方法的同一线程同步触发到客户端,而是从单独的工作线程触发,以避免与 .NET 发生潜在的死锁。此代码实现在 CNotifier
类中。
ThrowCOMError()
函数负责从嵌入的资源文件中提取丰富的错误描述,并将错误信息存储在当前线程上下文中,稍后 IChannelHook
接口将拾取它并将其发送到客户端的进程和线程上下文。
丰富错误信息如何传输到客户端?当执行进程外 COM 方法时,COM 库将发送带外(隐藏)信息以及封送的方法参数数据,如 Don Box 在 Microsoft Systems Journal 的文章中所述。它使用 IChannelHook
接口,前提是您已使用 CoRegisterChannelHook
安装了自己的钩子。我选择安装我自己的钩子,该钩子实现了将丰富错误对象从服务器传输回客户端的缺失功能。我已将此代码添加到代理/存根 DLL 中,以便每当调用接口时,此钩子都会自动安装。当然,如果您有多个接口要在不同的代理/存根 DLL 中进行封送,则最好将通道钩子实现在单独的 DLL 中,并在 Windows CE 映像启动过程的早期阶段进行注册,以便在需要时可用。下图说明了当 COM 方法调用返回错误条件并设置返回的 HRESULT
时实际发生的情况。
图 2. COM 丰富错误信息的程序流程
此解决方案允许在两个进程外 COM 应用程序之间传输丰富的错误信息,无论是客户端和服务器都运行在 Windows CE 上,还是其中一个运行在 Windows XP/2000 上(通过 DCOM)。
创建客户端
从 .NET CF 2.0 开始,Microsoft 添加了 CF 1.0 所缺失的 COM 互操作支持。这项出色的新功能促使我产生了创建一个 C# 用户界面客户端的想法,该客户端可以编译并用于 .NET Compact Framework 和桌面 .NET Framework。当然,在 Compact Framework 中,并非所有功能都与桌面变体相同,但如果您了解此限制并且首先编写 Compact Framework 版本,那么只要运气好,您的代码就可以在两个框架上毫无问题地编译。您需要做的就是添加对编译 COM EXE 服务器时生成的 TLB 文件的引用。现在,您可以编写自己的 C# 或 VB 客户端,并在 Windows XP/2000 和 Windows CE 上运行它们,连接到同一个 COM 服务器,该服务器同时在 Windows CE 上执行实时 C++ 代码。这难道不棒吗?设想有一天,在 Pocket PC 上也可以实现这一点……从 Pocket PC 控制您的家庭机器人……
不过,关于丰富错误信息有一点说明。在 .NET Framework 中,COMException
对象会立即在 Message
属性中提供错误描述,但在 Compact Framework 中则不会。因此,我创建了一个名为 ComError
的静态包装类,围绕 COM API GetErrorInfo()
函数,该函数在可用时检索此信息。此代码使用 P/Invoke 和手动封送来将错误对象信息呈现给应用程序,并以托管类的形式呈现。
拨号
当您的 Windows CE 映像在硬件上正确启动并且您可以将 Visual Studio 2005 连接到该映像时,就可以开始着手编写代码了。
Windows CE
首先,将 COM EXE 服务器和代理/存根 DLL 部署到您的设备上。接下来,确保代理/存根 DLL 已注册。您可以使用“RegSvrCE.exe /StarGatePS.dll”来完成此操作,或者让 Visual Studio 在部署时自动为您完成。EXE 服务器不会自动注册,因此您需要从 Windows CE 的命令 shell 中自行注册。运行“StarGate.exe /RegServer”。这将会在注册表中添加必要的条目。此时,您可以启动您的 Windows CE C# 用户界面并测试此部分。打开 ControlRoomCE.csproj,构建,部署并运行。
如果您按下“进入控制室”按钮,将创建 Gate 对象,您可以调用其“Dial gate”方法。如果您执行此操作超过七次,服务器将触发一个事件,告知网关已打开。如果您“Dial gate”超过八次,将返回错误消息。
远程连接
一旦我们的 COM EXE 服务器注册成功,我们就可以在 Windows CE 设备上启动 DCOMCnfg.exe。这是设置身份验证、访问和启动权限的地方。仅为本次测试,我们禁用了一些安全检查,以使生活暂时变得更容易。
尽管我们禁用了大多数默认安全检查,但我们仍需执行一项操作。在 Windows XP/2000 客户端发出 DCOM 调用到 Windows CE 服务器之前,DCOM 会尝试在 CE 设备上对客户端进行身份验证。它为此使用 NTLM。有两种可能性:
- 您的设备属于域,然后域服务器将为您进行身份验证,前提是您已将 [HKLM]\Comm\Redir\DefaultDomain = <your domain name> 注册表项添加到您的 Windows CE 设备。
- 或者您的设备不属于域,然后您必须创建一个本地 NTLM 帐户。您可以使用 NTLMUser.exe 来完成此操作。此工具将使用
SetNTLMUserInfo()
API 函数。输入与将运行客户端的用户(登录用户)相同的用户名和密码。
确保这两种解决方案(DefaultDomain
注册表项或本地 NTLM 帐户)不要同时设置,因为这不会起作用。有关更多信息,请参阅 Windows CE Platform Builder 帮助。
在 Windows CE 设备上,在“控制面板 | 所有者 | 网络 ID”选项卡中指定相同的用户名和密码也很有用,但并非必需。此信息仅供 Windows CE 在访问远程目录(例如,XP/2000 系统上的共享驱动器)时进行身份验证。不要将这些凭据与 NTLM 凭据混淆。两者服务于不同的目的。只有当您确实属于某个域时,才需要域参数。
Windows XP/2000
由于我们的接口仅使用自动化兼容的数据类型,因此我们无需在 Windows XP/2000 端注册代理/存根 DLL。请记住我们之前的讨论,此功能已由 oleaut32.dll 和 ole32.dll 提供。我们依靠内置的类型库封送器来完成工作。为了方便使用,我们注册类型库,以便 Visual Studio 可以立即查询它。运行 RegTlb.bat 来完成此操作。
Windows XP/2000 C# 客户端如何知道我们远程 COM EXE 服务器的位置?答案是注册表。如果我们向注册表输入 Gate 类信息,就可以通过 Windows XP/2000 上的 DCOMCnfg.exe 工具在其注册表中指定其位置。先运行 RegRGS.bat 将此信息输入注册表。接下来,在 Windows XP/2000 上运行 DCOMCnfg.exe 并搜索 Gate 类条目。右键单击并设置位置为您的 Windows CE 设备的 IP 地址。
请注意,Windows XP/2000 端没有任何 COM 服务器代码,只有注册表信息和通用 OLE Automation 封送器。
DCOM 的另一个值得注意的点是,当您启用防火墙时,请确保打开端口 135、137-139。DCOM RPC SCM 正在监听端口 135,并且需要 NETBIOS 名称解析才能使 DCOM 正常工作。此外,DCOM 需要打开 1023 以上的端口。
拨号
为了看到代码的实际效果,我们将组件在其各自的 Visual Studio 调试器中运行。
- 启动 Stargate.sln 并在 CE 设备上运行 COM EXE 服务器。
- 启动 ControlRoomCE.sln 并运行 CE 客户端。
- 同时启动 ControlRoomXP.sln 并运行 XP 客户端。
要了解 IChannelHook
接口的工作原理,请从代理/存根 DLL 启动调试器,并在 IChannelHook::ServerGetSize()
中设置断点。在服务器内部返回的每个进程外方法调用上,都会调用此钩子,允许您插入一些带外信息。在接收端(客户端),IChannelHook::ClientNotify()
是提取此信息的地方。相同的机制也以相反的方向可用,但不需要返回丰富的错误信息,但可以轻松用于其他 DCOM 调试目的。
祝您好运!