我希望每个人都会赦免这个问题的长度和叙事方式.我决定在我的博客中详细描述这种情况.后来我看到Joel对这个网站的邀请,我想我会把它粘贴在这里,看看有没有人对这种情况有所了解.
我编写并现在支持一个应用程序,该应用程序由一个Visual Basic胖客户端组成DCOM,使用ATL用C++编写的中间层COM +组件.它遍布我们所有的八个办事处.每个办公室都托管一个后端服务器,其中包含COM +应用程序(由18个独立组件组成)和SQLServer.SQLServer通常位于同一个后端服务器上,但不一定是这样.
我们最近将我们最大的办公室 - 纽约的后端服务器从MSC群集迁移到VMWare ESX技术上托管的新虚拟机.由于COM +应用程序的位置已从旧服务器移动到具有不同名称的新服务器,因此我不得不重定向所有客户端,以便它们在新服务器上激活COM +应用程序.这个过程很老套,因为我对几个经过类似基础设施升级的小型办公室做了同样的事情.
一切似乎都很常规,周一早上整个办公室 - 大约1,000个Windows XP工作站 - 在新服务器上运行时没有发生任何事故.但随后电话来自我的移动组 - 有一位律师在家工作,VPN连接在被重定向到新服务器后出现了一个奇怪的错误:
Error on FillTreeView2 - The stub received bad data.
咦?我以前从未见过这个错误信息.是新服务器吗?但办公室的所有工作站都运转正常.我告诉移动组将律师转回旧服务器(仍在运行),错误消失了.那有什么区别?原来这位律师在家里运行Vista.
我们不在任何办公室运行Vista,但我们确实有一些在家里运行Vista的律师(肯定是我在纽约办公室的一些).我也这样做,我从未见过这个问题.为了确认存在问题,我启动了我的Vista笔记本电脑,指向新服务器,并得到了同样的错误.我把它指回旧服务器,它工作正常.显然,Vista和新服务器上的组件存在一些问题 - 这个问题似乎不会影响XP客户端.会是什么呢?
下一站 - 笔记本电脑上的应用程序错误日志.这产生了有关错误的更多信息:
Source: Microsoft-Windows-RPC-Events Date: 9/2/2008 11:56:07 AM Event ID: 10 Level: Error Computer: DevLaptop Description: Application has failed to complete a COM call because an incorrect interface ID was passed as a parameter. The expected Interface ID was 00000555-0000-0010-8000-00aa006d2ea4, The Interface ID returned was 00000556-0000-0010-8000-00aa006d2ea4. User Action - Contact the application vendor for updated version of the application.
接口ID提供了解开这个谜团所需的线索."预期"接口ID标识MDAC的Recordset接口 - 特别是该接口的2.1版."返回"界面对应于Recordset的更高版本(版本2.5,与版本2.1的不同之处在于在vtable结尾处包含一个附加条目 - 方法保存).
实际上,我的组件的接口公开了许多将Recordset作为输出参数传递的方法.那么他们是否会突然返回Recordset的更高版本 - 具有不同的接口ID?情况确实如此.然后我想,为什么要重要.vtable看起来与旧接口的客户端相同.实际上,我怀疑如果我们讨论的是进程中的COM而不是DCOM,那么这种明显无害的阻抗不匹配就会被默默地忽略,并且不会引起任何问题.
当然,当进程和机器边界发挥作用时,客户端和服务器之间存在代理和存根.在这种情况下,我使用类型库编组与免费的线程编组.所以有两个谜团需要解决:
为什么我从新服务器上的方法返回输出参数中的不同接口?
为什么这只会影响Vista客户端?
由于我的服务器软件托管在我的八个办事处的每个办公室的服务器上,我决定尝试按顺序指向我的Vista客户端以查看哪些存在Vista问题,哪些问题没有.照明测试.一些较旧的服务器仍然使用Vista,但较新的服务器没有.虽然一些较旧的服务器仍在运行Windows 2000,而较新的服务器是在2003年,但这似乎不是问题.
在比较组件DLL的日期后,似乎每当客户端指向具有日期在2003之前的组件DLL的服务器时,Vista就可以了.但那些拥有2003年后日期的DLL的人是有问题的.相信它或者也许,多年来服务器组件上的代码没有(或至少没有重大的)变化.显然,不同的日期只是由于我的开发机器上的组件重新编译.似乎其中一次重新编译发生在2003年.
灯泡还在继续.将Recordset从服务器传递回客户端时,我的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对应于具有独特接口ID的2.5版本.我的解决方案是在我的C++代码中将_Recordset的所有引用更改为Recordset21.我重新构建了组件并将它们部署到新服务器.瞧 - 客户似乎又开心了.
总之,对我来说仍有两个棘手的问题.
为什么代理/存根基础结构似乎与Vista客户端的行为不同?似乎Vista正在对从方法参数返回的接口ID进行更严格的检查,而不是XP.
我应该如何在1999年对此进行不同的编码,以便不会发生这种情况?接口应该是不可变的,当我在更新版本的MDAC下重新编译时,我无意中更改了我的接口,因为这些方法现在返回一个不同的Recordset接口作为输出参数.据我所知,当时的类型库没有特定于版本的符号 - 也就是说,MDAC类型库的更高版本定义了Recordset21,但该符号在2.1类型库中不可用.