5

我正在使用具有自己的自定义接口的第三方 COM 服务器,该接口将结构设置和获取作为它的一些属性。碰巧我正在为客户端使用 C++。我从下面的 IDL 文件中发布了一些具有代表性的代码,其中更改了名称并删除了 GUID。

是否定义了结构的打包,或者我的客户端代码碰巧使用了与构建 COM 服务器相同的打包设置,这只是幸运?在默认 C++ 编译器打包设置已更改的项目中是否可能出错?我可以使用编译指示包设置来确保客户端编译器打包设置正确吗?

在 IDL 或从 MIDL 生成的头文件中,我看不到任何打包编译指示或语句。如果客户端使用 C# 或 VB 会发生什么?如果通过 IDispatch 机制调用,是否更清楚地指定了打包行为?

struct MyStruct
{
    int a, b;
};

[
    object,
    uuid( /* removed */ ),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IVideoOutputSettings : IDispatch{

    [propget, id(1), HRESULT MyProperty([out, retval] struct MyStruct* pVal);
    [propput, id(1), HRESULT MyProperty([in] struct MyStruct newVal);

    /* other methods */
};
4

2 回答 2

8

根据此处的 MIDL 命令行开关参考,默认打包沿 8 字节边界:

/Zp 开关@MSDN(MIDL 语言参考)

如果包值发生更改,您代码的其他部分更有可能首先中断,因为 IDL 文件通常是提前预编译的,很少有人会故意更改赋予 MIDL 的命令行开关(但不是如此罕见以至于有人可以摆弄 C 范围#pragma pack并忘记恢复默认状态)。

如果您有充分的理由更改设置,则可以使用pragma pack语句显式设置包装。

pragma 属性@MSDN(MIDL 语言参考)

幸运的是,没有任何一方更改任何会干扰默认打包的设置。会不会出错?是的,如果有人不遗余力地更改默认设置。

使用 IDL 文件时,详细信息通常会编译到类型库 (.tlb) 中,并且假定使用相同类型库时服务器和客户端的平台相同。这在/Zp开关的脚注中提出了建议,因为某些值将无法针对某些非 x86 或 16 位目标。也可能存在 32 位 <-> 64 位转换情况,这可能会导致预期中断。不幸的是,我不知道是否还有更多案例,但默认设置确实可以轻松工作。

C# 和 VB 没有任何内在行为来处理 .tlb 中的信息;相反,像 tlbimp 这样的工具通常用于将 COM 定义转换为可从 .NET 使用的定义。我无法验证 C#/VB.NET 与 COM 客户端和服务器之间的所有期望是否都成功;但是,如果您引用从在该设置下编译的 IDL 创建的 .tlb,我可以验证使用 8 以外的特定 pragma 设置是否有效。虽然我不建议您使用默认的编译指示包,但如果您希望将工作示例用作参考,请执行以下步骤。我创建了一个 C++ ATL 项目和一个 C# 项目来检查。

以下是 C++ 侧指令。

  1. 我使用 Visual Studio 2010 中的默认设置创建了一个名为SampleATLProject的 ATL 项目,没有更改任何字段。这应该为您创建一个 dll 项目。
  2. 编译项目以确保正在创建正确的 C 端接口文件(SampleATLProject_i.c 和 SampleATLProject_i.h)。
  3. 我添加了一个调用SomeFoo到项目的 ATL 简单对象。同样,没有更改默认值。这将创建一个名为的类CSomeFoo,并将其添加到您的项目中。
  4. 编译示例 ATL 项目。
  5. 我右键单击 SampleATLProject.idl 文件,然后在 MIDL 设置下,将 Struct Member Alignment 设置为 4 个字节 (/Zp4)。
  6. 编译示例 ATL 项目。
  7. 我更改了 IDL 以添加一个名为“BarStruct”的结构定义。这需要添加一个具有 MIDL uuid 属性的 C 样式结构定义,以及引用该结构定义的库部分中的一个条目。请参阅下面的片段。
  8. 编译示例 ATL 项目。
  9. 在类视图中,我右键单击ISomeFoo并添加了一个名为 的方法FooIt,该方法将 astruct BarStruct作为名为theBar的 [in] 参数。
  10. 编译示例 ATL 项目。
  11. 在 SomeFoo.cpp 中,我添加了一些代码来打印结构的大小并抛出一个包含详细信息的消息框。

这是我的 ATL 项目的 IDL。

import "oaidl.idl";
import "ocidl.idl";

[uuid(D2240D8B-EB97-4ACD-AC96-21F2EAFFE100)]
struct BarStruct
{
  byte a;
  int b;
  byte c;
  byte d;
};

[
  object,
  uuid(E6C3E82D-4376-41CD-A0DF-CB9371C0C467),
  dual,
  nonextensible,
  pointer_default(unique)
]
interface ISomeFoo : IDispatch{
  [id(1)] HRESULT FooIt([in] struct BarStruct theBar);
};
[
  uuid(F15B6312-7C46-4DDC-8D04-9DEA358BD94B),
  version(1.0),
]
library SampleATLProjectLib
{
  struct BarStruct;
  importlib("stdole2.tlb");
  [
    uuid(930BC9D6-28DF-4851-9703-AFCD1F23CCEF)      
  ]
  coclass SomeFoo
  {
    [default] interface ISomeFoo;
  };
};

CSomeFoo类内部,这里是FooIt().

STDMETHODIMP CSomeFoo::FooIt(struct BarStruct theBar)
{
  WCHAR buf[1024];
  swprintf(buf, L"Size: %d, Values: %d %d %d %d", sizeof(struct BarStruct), 
           theBar.a, theBar.b, theBar.c, theBar.d);

  ::MessageBoxW(0, buf, L"FooIt", MB_OK);

  return S_OK;
}

接下来,在 C# 方面:

  1. 转到 SampleATLProject 的调试或所需输出目录,并在作为 C++ 项目输出的一部分生成的 .tlb 文件上运行 tlbimp.exe。以下对我有用:

    tlbimp SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff

  2. 接下来,我创建了一个 C# 控制台应用程序,并将对 Foo.dll 的引用添加到项目中。

  3. 在 References 文件夹中,转到 Properties for并通过将Embed Interop Types设置为 false 来Foo关闭它。
  4. 我添加了一条 using 语句来引用SampleATL.FooStuff给定给 tlbimp 的命名空间,添加了[STAThread]属性Main()(COM 单元模型必须与进程内消费相匹配),并添加了一些代码来调用 COM 组件。

Tlbimp.exe(类型库导入器)@ MSDN

这是该控制台应用程序的源代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SampleATL.FooStuff;

namespace SampleATLProjectConsumer
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            BarStruct s;
            s.a = 1;
            s.b = 127;
            s.c = 255;
            s.d = 128;

            ISomeFoo handler = new SomeFooClass();
            handler.FooIt(s);
        }
    }
}

最后,它运行,我得到一个显示以下字符串的模式弹出窗口:

Size: 12, Values: 1 127 255 128

为了确保可以进行编译指示包更改(因为 4/8 字节打包是最常用的对齐方式),我按照以下步骤将其更改为 1:

  1. 我返回到 C++ 项目,转到 SampleATLProject.idl 的属性并将 Struct Member Alignment 更改为 1 (/Zp1)。
  2. 重新编译 SampleATLProject
  3. 使用更新的 .tlb 文件再次运行 tlbimp。
  4. .NET 文件引用上将出现一个警告图标Foo,但如果单击该引用,它可能会消失。如果没有,您可以删除并重新添加对 C# 控制台项目的引用,以确保它使用的是新的更新版本。

我从这里运行它并得到了这个输出:

Size: 12, Values: 1 1551957760 129 3

这很奇怪。但是,如果我们在 SampleATLProject_i.h 中强制编辑 C 级编译指示,我们会​​得到正确的输出。

#pragma pack(push, 1)
/* [uuid] */ struct  DECLSPEC_UUID("D2240D8B-EB97-4ACD-AC96-21F2EAFFE100") BarStruct
    {
    byte a;
    int b;
    byte c;
    byte d;
    } ;
#pragma pack(pop)

SampleATLProject is recompiled here, no changes to the .tlb or .NET project, and we get the following:

Size: 7, Values: 1 127 255 128

Regarding IDispatch, it depends on whether your client is late-bound. Late-bound clients have to parse the type information side of IDispatch and discern the proper definitions for non-trivial types. The documentation for ITypeInfo and TYPEATTR suggests that it is possible, given that the cbAlignment field provides the information necessary. I suspect most will never alter or go against the defaults, as this would be tedious to debug if things went wrong or if the pack expectations had to change between versions. Also, structures are not typically supported by many scripting clients that can consume IDispatch. One can frequently expect that only the types governed by the IDL oleautomation keyword are supported.

IDispatch interface @ MSDN
IDispatch::GetTypeInfo @ MSDN
ITypeInfo interface @ MSDN
TYPEATTR structure @ MSDN

oleautomation keyword @ MSDN

于 2012-04-13T23:59:55.500 回答
7

Yes, structs are a problem in COM. If you use IUnknown based interfaces then you'll have to roll the dice with proper compiler settings. Few reasons to change the default.

If you use COM Automation then you have to declare the struct with a typedef in the .IDL. So that the client code can use IRecordInfo to access the structure properly, guided by the type library info. All you have to do is ensure that your compiler's /Zp setting matches midl.exe's /Zp setting. Not hard to do.

You sail around the problem entirely by realizing that any structure can be described by an interface with properties. Now it doesn't matter.

于 2012-04-14T00:44:40.517 回答