5

在我的 VB6 应用程序中,我多次调用我的团队从 Ada 项目(使用 GNATCOM)创建的 COM 服务器。COM 服务器上基本上有 2 种可用的方法。他们在 VB 中的原型是:

Sub PutParam(Param As Parameter_Type, Value)
Function GetParam(Param As Parameter_Type)

其中Parameter_Type是一个枚举类型,它区分了我可以从 COM 服务器输入/获取的许多参数,而“Value”是一个Variant类型变量。PutParam() 接收一个变体,GetParam() 返回一个变体。(我真的不知道为什么在 VB6 对象浏览器中没有对 COM 服务器接口上的 Variant 类型的引用......)。

该项目的产品多年来一直以这种方式连续使用,在装有 Windows XP SP2 的计算机上,此界面没有任何问题。在装有 WinXP SP3 的计算机上,当尝试将参数设置为“长”类型时,我们会收到错误 0x800706F7“存根收到错误数据”。

有没有人知道可能导致这种情况的原因?COM 服务器仍在使用 SP2 的系统中构建。在带有 SP3 的系统上构建它应该有什么不同吗?(就像我们在 X64 系统中为 X64 构建时一样)。

导致问题的调用之一如下(更改了一些 var 名称):

Dim StructData As StructData_Type

StructData.FirstLong = 1234567
StructData.SecondLong = 8901234
StructData.Status = True

ComServer.PutParam(StructDataParamType, StructData)

其中 StructData_Type 的定义是:

Type StructData_Type
    FirstLong As Long
    SecondLong As Long
    Status As Boolean
End Type

(以下已在问题首次发布后添加)

IDL中COM服务器接口的原语调用定义如下:

// Service to receive data
HRESULT PutParam([in] Parameter_Type Param, [in] VARIANT *Value);

//Service to send requested data
HRESULT GetParam([in] Parameter_Type Param, [out, retval] VARIANT *Value);

我试图通过的结构的定义是:

struct StructData_Type
{
   int FirstLong;
   int SecondLong;
   VARIANT_BOOL Status;
} StructData_Type;

我发现奇怪的是,这里的定义使用“int”作为 FirstLong 和 SeconLong 的类型,当我检查 VB6 对象资源管理器时,它们的类型是“Long”。顺便说一句,当我从 COM 服务器中提取 IDL(使用特定实用程序)时,这些参数被定义为 Long。

更新:

我已经使用为 Windows 7 编译的 COM 服务器版本(不同版本的 GNAT,相同的 GNATCOM 版本)测试了相同的代码,它可以工作!我真的不知道这里发生了什么。我将继续尝试找出 WinXP SP3 上的问题,但很高兴知道它在 Win7 上有效。如果您有类似的问题,最好尝试迁移到 Win7。

4

4 回答 4

8

我将重点解释错误的含义,问题中的提示太少,无法提供简单的答案。

当您跨执行边界进行调用时,在 COM 中使用“存根”。问题中没有明确说明,但您的 Ada 程序可能是 EXE 并实现了进程外 COM 服务器。在 Windows 中跨越进程之间的边界是很困难的,因为它们具有很强的隔离性。这是在 Windows 中通过 RPC(远程过程调用)来完成的,这是一种用于跨边界进行调用的协议,典型的例子是网络。

要进行 RPC 调用,必须将函数的参数序列化为网络数据包。COM 不知道如何做到这一点,因为它对函数的实际参数知之甚少,它需要代理的帮助。一段确实知道参数类型是什么的代码。在接收端是一段非常相似的代码,它的作用与代理的作用完全相反。它反序列化参数并进行内部调用。这是存根。

这可能失败的一种方法是当存根接收到网络数据包并且它包含比函数参数值所需的数据更多或更少的数据时。显然,它不知道如何处理该数据包,没有明智的方法将其转换为 StructData_Type 值,并且它将因“存根收到错误数据”错误而失败。

所以要考虑的这个错误的第一个解释是 DLL Hell 问题。代理和存根之间不匹配。如果这个应用程序已经稳定了很长时间,那么这不是一个令人愉快的解释。

您的代码片段的另一个方面可能会导致此问题。结构是软件中非常麻烦的野兽,它们的成员是对齐的到它们的自然存储边界和对齐规则由各自的编译器解释。您引用的结构肯定就是这种情况。它需要 10 个字节来存储字段,4 + 4 + 2 并且它们自然对齐。但该结构实际​​上是 12 字节长。最后填充两个字节以确保当结构存储在数组中时整数仍然对齐。这也使 COM 的工作变得非常困难,因为 COM 隐藏了实现细节并且结构对齐是一个巨大的细节。它需要帮助来复制一个结构,即 IRecordInfo 接口的工作。当找不到该接口的实现时,存根也会失败。

我将谈谈代理、存根和IRecordInfo。生成代理/存根对有两种基本方式。一种方法是用一种称为 IDL(接口描述语言)的语言来描述接口,然后用 MIDL 编译它。该编译器能够自动生成代理/存根代码,因为它知道函数参数类型。您将获得一个需要在客户端和服务器上注册的 DLL。您的服务器可能正在使用它,我不知道。

第二种方式是 VB6 使用的,它利用了内置于 Windows 中的通用代理。称为 FactoryBuffer,其 CLSID 为 {00000320-0000-0000-C000-000000000046}。它通过使用类型库来工作。类型库是 COM 服务器中函数的机器可读描述,足以让 FactoryBuffer 弄清楚如何序列化函数参数。这个类型库也是提供 IRecordInfo 需要了解结构成员如何对齐的信息的类型库。我不知道它在服务器端是如何完成的,以前从未听说过 GNATCOM。

所以对这个问题的一个强有力的解释是你的类型库有问题。在 VB6 中尤其棘手,因为您无法直接控制它使用的 guid。当您进行微不足道的更改时,它喜欢生成新的,避免它的唯一方法是选择二进制兼容性选项。它使用类型库的旧副本,并尝试使新副本尽可能兼容。如果您没有打开该选项,那么确实会遇到麻烦,尤其是对于结构的指导。Kaboom 如果它发生了变化并且另一端仍在使用旧的 guid。

只是一些关于从哪里开始寻找的提示。不要以为这是 SP3 引起的问题,这个 COM 基础结构已经很长时间没有改变了。但是由于安装了新的操作系统版本并且必须重新注册所有内容,因此肯定会出现这种问题。SysInternals 的 ProcMon 是一个很好的实用程序,可以查看程序使用注册表来查找代理、存根和类型库。而且您肯定会从 COM Spy 类型的实用程序中获得帮助,尽管这些天很难找到它们。

于 2013-04-10T09:49:24.493 回答
0

我还建议问题是由于您的结构中的填充问题。我不知道您是否可以使用#pragma 来控制它,但可能值得查看您的文档。

我认为尝试修补您的结构是个好主意,以便生成的类型库结构是四(或八)的倍数。您的 Status 成员占用 2 个字节,所以也许您应该在 Status 之前或之后插入一个相同类型的虚拟值 - 这应该使它达到 12 个字节(如果打包到 8 个字节,这必须是三个虚拟变量) .

于 2013-04-12T12:29:01.777 回答
0

如果它突然停止在 XP 上愉快地工作,我要寻找的第一个罪魁祸首是类型不匹配。此类系统上的“long”现在可能是 64 位,而您的 Ada COM 代码(和/或您的 C int)可能需要 32 位。对于传统编译的系统,编译器会为您检查这一点,但是您使用 COM 的额外间接性使这变得困难。

您在那里写的关于“当我们为 64 位系统编译时”的内容让我特别怀疑。你知道,64 位编译可能会改变许多 C 类型的大小。

于 2013-04-09T13:46:53.417 回答
0

This Related Post建议您需要在结构中进行填充,因为编组代码可能需要比您实际发送的数据更多的数据(当然,这是一个错误)。您的结构包含 9 个字节(假设每个 ints/long 有 4 个字节,布尔值有 1 个字节)。尝试添加填充,以便您的结构包含 4 个字节的倍数(或者,如果失败,则为 8 的倍数,因为帖子的预期大小不清楚)

于 2013-04-10T08:52:12.117 回答