65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (10投票s)

2006 年 4 月 17 日

CPOL

14分钟阅读

viewsIcon

100030

downloadIcon

528

本文演示了如何在 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.dlloleaut32.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.dllole32.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 调试目的。

祝您好运!

参考文献

© . All rights reserved.