Visual c++ 是否定义了COM接口中传递的结构打包?

Visual c++ 是否定义了COM接口中传递的结构打包?,visual-c++,com,idl,Visual C++,Com,Idl,我正在使用一个第三方COM服务器,它有自己的自定义接口,可以设置和获取结构作为它的一些属性。碰巧,我正在为客户端使用C++。我在下面的IDL文件中发布了一些具有代表性的代码,其中更改了名称并删除了GUID 是定义了结构的打包,还是我的客户机代码碰巧使用了COM服务器构建时使用的打包设置?在默认的C++编译器打包设置中更改的项目是否可能出错?是否有pragma pack设置可用于确保客户端编译器打包设置正确 在IDL或MIDL生成的头文件中,我看不到任何打包pragma或语句。如果客户端使用C#或

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

是定义了结构的打包,还是我的客户机代码碰巧使用了COM服务器构建时使用的打包设置?在默认的C++编译器打包设置中更改的项目是否可能出错?是否有pragma pack设置可用于确保客户端编译器打包设置正确

在IDL或MIDL生成的头文件中,我看不到任何打包pragma或语句。如果客户端使用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 */
};

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

如果更改包值,代码的其他部分更有可能首先中断,因为IDL文件通常是提前预编译的,而且很少有人会故意更改提供给MIDL的命令行开关(但有人会摆弄C-scope
#pragma pack
而忘记恢复默认状态的情况并不罕见)

如果有很好的理由更改设置,可以使用
pragma pack
语句显式设置打包

幸运的是,没有任何一方更改了任何会干扰默认打包的设置。它会出错吗?是的,如果有人特意更改默认打包

当使用IDL文件时,详细信息通常被编译成类型库(.tlb),并且假设在使用相同的typelib时,服务器和客户端的平台是相同的。
/Zp
开关的脚注中建议了这一点,因为某些值在某些非x86或16位目标上会失败。也可能存在32位64位转换情况,可能会导致期望值中断。不幸的是我不知道是否还有更多的情况出现,但默认的情况下,最小的麻烦工作

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

这里是C++的侧指令。< /P>

  • 我使用Visual Studio 2010中的默认设置创建了一个名为SampleATLProject的ATL项目,未更改任何字段。这将为您创建一个dll项目
  • 编译项目以确保创建正确的C端接口文件(SampleATLProject_i.C和SampleATLProject_i.h)
  • 我在项目中添加了一个名为
    SomeFoo
    的ATL简单对象。同样,没有更改默认值。这将创建一个名为
    CSomeFoo
    的类,并将其添加到项目中
  • 编译SampleATLProject
  • 我右键单击SampleATLProject.idl文件,然后在MIDL设置下,将结构成员对齐设置为4字节(/Zp4)
  • 编译SampleATLProject
  • 我修改了IDL以添加一个名为“BarStruct”的结构定义。这需要添加一个具有MIDL uuid属性的C样式结构定义,以及库部分中引用结构定义的条目。请参阅下面的代码段
  • 编译SampleATLProject
  • 在类视图中,我右键单击了
    isomofoo
    ,并添加了一个名为
    FooIt
    的方法,该方法将
    struct BarStruct
    作为名为theBar的[in]参数
  • 编译SampleATLProject
  • 在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()
    的实现

    接下来,在C侧:

    >P>转到SampleATLProject的调试或期望输出目录,并运行作为C++项目输出的一部分生成的.TLB文件。 tlbimp SampleATLProject.tlb/out:Foo.dll/namespace:SampleATL.FooStuff

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

  • 在References文件夹中,转到
    Foo
    的属性,并通过将其设置为false禁用嵌入互操作类型
  • 我添加了一个using语句来引用给定给tlbimp的名称空间
    SampleATL.FooStuff
    ,将
    [STAThread]
    属性添加到
    Main()
    (COM单元模型必须与进程内消费相匹配),并添加了一些代码来调用COM组件
  • 以下是该控制台应用程序的源代码

    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
    
    为了确保可以更改pragma pack(因为4/8字节打包是最常用的对齐方式),我按照以下步骤将其更改为1:

    我返回C++项目,转到SampleATLProject.idl的属性,并将StReT成员对齐更改为1(/ZP1)。
  • 重新编译SampleATLProject
  • 使用更新的.tlb文件再次运行tlbimp
  • 屏幕上将显示一个警告图标
    Size: 12, Values: 1 127 255 128
    
    Size: 12, Values: 1 1551957760 129 3
    
    #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)
    
    Size: 7, Values: 1 127 255 128