2012-04-13 88 views
3

我正在与第三方COM服务器与自己的自定义接口,设置和获取结构的一些属性。碰巧我使用C++作为客户端。我从以下IDL文件中发布了一些代表性代码,其中更改了名称并删除了GUID。在COM接口中传递的结构包装是否已定义?

是否定义了结构的包装,还是我的客户端代码碰巧使用了与COM服务器一起构建的相同包装设置?在默认的C++编译器打包设置被更改的项目中,它可能会出错吗?是否有可用于确保客户端编译器打包设置正确的编译包设置?

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

回答

6

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

/Zp switch @ MSDN (MIDL Language Reference)

你的代码的其他部分更容易,如果包先破因为IDL文件通常会提前预编译,而且很少有人会故意改变给MIDL的命令行开关(但不是很少有人可以摆弄C范围#pragma pack而忘记了恢复默认状态)。

如果您有充分的理由更改设置,则可以使用pragma pack声明明确设置包装。

pragma Attribute @ MSDN (MIDL Language Reference)

这是相当好运气,任何一方发生了变化,将使用默认的包装干涉任何设置。它会出错吗?是的,如果有人不想改变默认设置。

使用IDL文件时,详细信息通常会编译到typelib(.tlb)中,并且假定使用相同typelib时,平台对于服务器和客户端都是相同的。这在/Zp开关的脚注中被提出,因为某些值将针对某些非x86或16位目标而失败。也可能有32位< - > 64位转换情况,可能会导致期望中断。不幸的是,我不知道是否有更多的案例,但默认情况下工作最少。

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

这里是C++端说明。

  1. 我创建了一个名为SampleATLProject在Visual Studio 2010中的默认设置的ATL项目,没有任何字段改变。这应该为你创建一个dll项目。
  2. 编译项目以确保正在创建正确的C端接口文件(SampleATLProject_i.c和SampleATLProject_i.h)。
  3. 我在项目中添加了一个名为SomeFoo的ATL简单对象。再次,没有违约被改变。这会创建一个名为CSomeFoo的类,并将其添加到您的项目中。
  4. 编译SampleATLProject。
  5. 我右键单击SampleATLProject.idl文件,然后在MIDL设置下将Struct Member Alignment设置为4个字节(/ Zp4)。
  6. 编译SampleATLProject。
  7. 我改变了IDL来添加一个名为'BarStruct'的结构定义。这需要在MIDL uuid属性中添加C风格的结构定义,并在库部分中引用结构定义。请参阅下面的代码片段。
  8. 编译SampleATLProject。
  9. 从类视图,我右键点击ISomeFoo和添加了一个名为FooIt方法,采用一个struct BarStruct作为称为theBar一个[in]参数。
  10. 编译SampleATLProject。
  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. 在参考文件夹中,转至Foo的属性并关闭将互操作类型设置为false。
  4. 我添加了一个using语句来引用命名空间SampleATL.FooStuff给予tlbimp,将[STAThread]属性添加到Main()(COM模型必须匹配进程内消耗),并添加了一些代码来调用COM组件。

Tlbimp.exe (Type Library Importer) @ 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字节包装是使用最普遍的比对),I按照这些步骤将其改为1:

  1. 我返回到C++项目,就到了属性SampleATLProject.idl,改变了结构体成员对齐为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这里重新编译,无需改变.TLB或.NET项目,我们得到如下:

Size: 7, Values: 1 127 255 128 

关于IDispatch,这取决于你的客户端是后期绑定。晚期客户必须解析IDispatch的类型信息,并辨别非平凡类型的正确定义。 ITypeInfoTYPEATTR的文档表明,如果cbAlignment字段提供了必要的信息,那么这是可能的。我怀疑大多数人永远不会改变或违背默认设置,因为如果事情出错或者版本之间的软件包期望不得不改变,那么调试会很乏味。此外,许多脚本客户端通常不支持结构,这些客户端可能会使用IDispatch。人们经常会期望只支持IDL oleautomation关键字所支持的类型。

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

oleautomation keyword @ MSDN

5

是的,结构是在COM的一个问题。如果你使用基于IUnknown的接口,那么你必须使用适当的编译器设置来掷骰子。很少有理由改变默认值。

如果您使用COM自动化,则必须在.IDL中用typedef声明结构。这样客户端代码就可以使用IRecordInfo正确访问结构,并由类型库信息指导。您所要做的就是确保您的编译器的/ Zp设置与midl.exe的/ Zp设置匹配。不难做到。

您完全意识到任何结构都可以通过具有属性的接口进行描述,从而解决了这个问题。现在没关系。

相关问题