5

我希望每个人都能原谅这个问题的长度和叙述方式。我决定在我的博客中详细描述一下这种情况。后来我看到乔尔对这个网站的邀请,我想我会把它贴在这里看看是否有人对这种情况有任何见解。

我编写了一个应用程序,现在支持一个应用程序,该应用程序包含一个 Visual Basic 胖客户端,它将 DCOM 与使用 ATL 用 C++ 编写的中间层 COM+ 组件进行通信。它在我们所有的八个办公室中运行。每个办公室都有一个后端服务器,其中包含 COM+ 应用程序(由 18 个独立的组件组成)和 SQLServer。SQLServer 通常位于同一后端服务器上,但不一定如此。

我们最近将我们最大的办公室——纽约——的后端服务器从一个 MSC 集群迁移到了一个托管在 VMWare 的 ESX 技术上的新虚拟机。由于 COM+ 应用程序的位置已从旧服务器移动到具有不同名称的新服务器,因此我必须重定向所有客户端,以便它们在新服务器上激活 COM+ 应用程序。该程序是旧帽子,因为我对经历过类似基础设施升级的几个较小的办公室做了基本相同的事情。

一切看起来都很正常,周一早上,整个办公室——大约 1,000 台 Windows XP 工作站——都在新服务器上顺利运行。但随后电话来自我的移动组——有一位在家工作的律师使用 VPN 连接,在被重定向到新服务器后出现奇怪的错误:

FillTreeView2 上的错误 - 存根收到错误数据。

嗯?我以前从未见过此错误消息。是新服务器吗?但是办公室里的所有工作站都工作正常。我告诉移动组将律师切换回旧服务器(仍在运行),错误消失了。那么有什么区别呢?原来这位律师正在家里运行 Vista。

我们的任何办公室都没有运行 Vista,但我们确实有一些在家里运行 Vista 的律师(当然有些在我的纽约办公室)。我也这样做,我从未见过这个问题。为了确认存在问题,我启动了我的 Vista 笔记本电脑,将其指向新服务器,并得到了同样的错误。我将它指向旧服务器,它运行良好。显然,Vista 和新服务器上的组件存在一些问题——这个问题似乎不会影响 XP 客户端。会是什么呢?

下一站——我的笔记本电脑上的应用程序错误日志。这产生了有关错误的更多信息:

来源:Microsoft-Windows-RPC-Events
日期:2008 年 9 月 2 日上午 11:56:07
事件编号:10
级别:错误
电脑:开发笔记本电脑
说明:应用程序未能完成 COM 调用,因为不正确
接口 ID 作为参数传递。

预期的接口 ID 为 00000555-0000-0010-8000-00aa006d2ea4,
返回的接口 ID 为 00000556-0000-0010-8000-00aa006d2ea4。

用户操作 - 联系应用程序供应商以获取应用程序的更新版本。

界面 ID 提供了我解开谜团所需的线索。“预期”接口 id 标识 MDAC 的 Recordset 接口——特别是该接口的 2.1 版。“返回”接口对应于 Recordset 的更高版本(2.5 版与 2.1 版的不同之处在于在 vtable 的末尾包含了一个附加条目——方法 Save)。

实际上,我的组件接口公开了许多将 Recordset 作为输出参数传递的方法。那么他们是否突然返回了更高版本的 Recordset —— 具有不同的接口 ID?显然情况确实如此。然后我想,这有什么关系。对于旧接口的客户端,vtable 看起来是一样的。事实上,我怀疑如果我们谈论的是进程内 COM,而不是 DCOM,这种看似无害的阻抗失配会被默默地忽略,不会造成任何问题。

当然,当进程和机器边界发挥作用时,客户端和服务器之间存在代理和存根。在这种情况下,我将类型库编组与自由线程编组器一起使用。所以有两个谜团需要解开:

为什么我在新服务器上的方法的输出参数中返回了不同的接口?

为什么这只会影响 Vista 客户端?

由于我的服务器软件托管在我的八个办公室的每个服务器上,我决定尝试按顺序将我的 Vista 客户端指向所有这些办公室,以查看哪些与 Vista 有问题,哪些没有。光照测试。一些较旧的服务器仍可与 Vista 一起使用,但较新的服务器则不能。尽管一些较旧的服务器仍在运行 Windows 2000,而较新的服务器在 2003 年,但这似乎不是问题。

在比较了组件 DLL 的日期之后,似乎每当客户端指向具有 2003 年之前的组件 DLL 的服务器时 Vista 都可以。但是那些拥有日期在 2003 年之后的 DLL 是有问题的。信不信由你,多年来,服务器组件上的代码没有(或至少没有重大)变化。显然,不同的日期仅仅是由于在我的开发机器上重新编译了我的组件。似乎其中一个重新编译发生在 2003 年。

灯泡亮了。在将记录集从服务器传回客户端时,我的 ATL C++ 组件将接口称为 _Recordset。此符号来自嵌入在 msado15.dll 中的类型库。这是我在 C++ 代码中的行:

#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename ( "EOF", "adoEOF" )

不要被msdad15.dll中的15个骗了。显然,这个 DLL 在长系列的 MDAC 版本中没有更改名称。

那天我编译应用程序时,MDAC 的版本是 2.1。所以 _Recordset 使用 2.1 接口 id 编译,即运行这些组件的服务器返回的接口。

所有客户端都使用 1999 年生成(我相信)的 COM+ 应用程序代理。定义我的接口的类型库包括以下行:

importlib("msado21.tlb");

这解释了为什么他们希望在我的方法的输出参数中使用 Recordset 2.1 版。很明显,问题出在我 2003 年的重新编译上,而且当时 _Recordset 符号不再对应于 2.1 版。实际上 _Recordset 对应于 2.5 版本,其接口 id 不同。我的解决方案是在我的 C++ 代码中将所有引用从 _Recordset 更改为 Recordset21。我重建了组件并将它们部署到新服务器。瞧——客户似乎又高兴了。

总之,有两个困扰我的问题留给我。

为什么代理/存根基础结构似乎与 Vista 客户端的行为不同?与 XP 相比,Vista 似乎对从方法参数返回的接口 ID 进行了更严格的检查。

我应该如何在 1999 年以不同的方式对此进行编码,以免发生这种情况?接口应该是不可变的,当我在较新版本的 MDAC 下重新编译时,我无意中更改了我的接口,因为这些方法现在返回了不同的 Recordset 接口作为输出参数。据我所知,当时的类型库没有特定于版本的符号——也就是说,更高版本的 MDAC 类型库定义了 Recordset21,但该符号在 2.1 类型库中不可用。

4

1 回答 1

2

当微软获得安全信仰时,DCOM(和底层的 RPC)得到了很多关注,并且肯定有一些改变来关闭导致更严格的编组的安全漏洞。我很惊讶您在 Vista 中看到了这一点,但在 XP 中却没有,但有可能为 Vista 添加了额外的检查。或者,XP中的可选严格性可能在Vista中成为强制性的。

虽然我对 MDAC 了解得不够多,无法知道您是否可以阻止这种情况,但我确实知道安全是微软非常愿意牺牲向后兼容性的少数几个领域之一,因此您可能无法做任何事情”更好”早在 1999 年。

于 2008-09-18T06:10:31.627 回答