是否可以在COM接口的C#实现中指定struct packing size?
(我知道如何在托管端定义结构时执行此操作,但我的问题是关于何时在非托管端定义并且实现管理方。)
我有一个COM类型库,它定义了一个结构类型和一个返回这些结构数组的接口方法。我有一个C#服务器和一个非托管C ++服务器,它们都实现了这个接口,以及一个消耗它的C ++客户端。 C ++服务器和C ++客户端都将结构打包为32位版本中的4个字节,以及64位版本中的8个字节。
但无论平台(x86,x64,AnyCPU)如何,C#服务器始终打包为4个字节。这是正常的吗?它可以被覆盖吗?
结构如下所示:
typedef [v1_enum] enum { blah... } HandlerPriority;
struct HandlerInfo { BSTR MessageName; HandlerPriority Priority; }
Visual Studio C ++和MIDL编译器使用/ Zp8的默认打包。在32位构建中,结构的两个成员都是4个字节宽,因此它们不会被填充。在64位构建中,字符串指针是8个字节,枚举4,因此枚举被填充。当然,这会在C#客户端发送未填充数据时导致问题。
我可以通过指定/ Zp4删除填充来修复(解决?)问题,一切似乎都能正常工作。但我想知道这是否是最好的解决方案。
我认为默认打包是/ Zp8仅出于性能原因。据我了解,默认情况下在x64上硬件陷阱和处理对齐异常,所以至少我们不会崩溃。在这种特殊情况下,我不关心性能损失,因为接口函数仅在系统启动时调用。即使我确实关心,我仍然可以接受它作为COM / .NET互操作的成本。但我有点不安,因为它感觉不对(我猜想来自C ++背景。)
另一方面,如果根本无法更改管理方的包装,那么我会接受它。
有人能给我一些建议吗?
答案 0 :(得分:2)
包装大小由它生成的RCW中的tlbimp.exe指定。如果我在命令行上传递/ platform:x64,那么RCW会说“.pack 8”,但是如果我说/ platform:x86或/ platform:不可知,那么它会说“.pack 4”。
我确实想要/ platform:不可知,这样我就可以在32位和64位平台上使用相同的RCW。通常我发现AnyCPU比它的价值更麻烦,但是这个项目是一个SDK,如果我可以避免,我不想在我的用户上强加我对该主题的看法。
我还想要8字节打包,因为x64上的4字节打包可能很昂贵。请参阅http://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx。
我已经解决的解决方案是反编译RCW,在生成的源代码中更改.pack指令,然后重新编译。
答案 1 :(得分:2)
我们有一个类似的问题,一些结构,当从C#传递到C ++通过COM,在32位COM中工作,但在64位中被破坏。
我花了一些时间研究,这个帖子也很有帮助。
看起来TlbImp.exe尝试将每个结构打包为4字节对齐,并且只有当它不符合某些条件时,才会将结构打包为8字节对齐。它对32位和64位模式都这样做。
但确定它有一些奇怪的规则。我发现,对于x64,如果一个结构只有4字节的整数,枚举和安全阵列,它被打包为4字节对齐,否则它打包为8字节的alinged。对于x86,当一个64位变量存在时,结构只被打包为8字节对齐,否则,它被打包为4字节对齐。也许还有其他变种,但这只是我们在产品中发现的。
另一方面,C ++使用默认打包(8个字节),每个类型包装至少8个字节或成员的大小。
因此,对于32位模式,没有问题,因为如果结构中没有8字节大小的成员,则对齐4到8个字节之间没有区别。但如果有,TlbImp.exe将结构打包为8字节对齐,所以这也匹配C ++。
但是,对于64位模式,有一种情况,当一个结构有8个字节的成员,但打包为4字节对齐。在我们的例子中,它是SAFEARRAYs。如果一个结构只有SAFEARRAY和32-(或更少?) - 位整数,那么它被打包为x64平台的4字节对齐。但是,由于SAFEARRAY是C ++端的64位指针,因此它以不同方式打包,在SAFEARRAY之前填充区域为8字节边距。我不知道,无论是错误还是功能。
为解决这个问题,我们在64位的4字节对齐结构之前注入了C ++编译指示包指令。我们通过使用ildasm.exe反编译程序集并搜索“.pack 4”来识别此类结构(感谢Ciaran提示)。
IDL中的这种结构如下所示:
// some of our structures must be 4 bytes aligned because TlbImp packs them this way
cpp_quote("#pragma pack(push, 4)")
typedef [uuid("3F253C09-D7F5-3BE-9698-00CB49A7005C"),
helpstring("SOME structure"),
version(5.1)] struct SOMEINFO
{
long ItemsActive;
SAFEARRAY(BSTR) ItemIDs; // without pack 4 it would be 8 bytes aligned on x64
SAFEARRAY(long) ItemRuns;
SAFEARRAY(SOMEFunctions) ItemFuncList;
SAFEARRAY(VARIANT) ItemFactors;
long MIndex;
SAFEARRAY(VARIANT) DeltaItems;
} SOMEINFO;
cpp_quote("#pragma pack(pop)") // matches pack(push, 4) placed before the structure
或者,我们可以在这个结构中添加一个64位虚拟变量,或BSTR强制它进入.pack 8,但我们不想更改接口。
所以,我们基本上遵循TlbImp它的奇怪逻辑。但是我们的代码中只有一些这样的结构,所以对我们来说没问题。这样,32位和64位COM都能正确传递所有结构。
答案 2 :(得分:1)
Pack属性是否适合您?这是我自己的代码中的一个例子:
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct TOKEN_PRIVILEGES
{
public int privilegeCount;
public LUID_AND_ATTRIBUTES privileges;
}
我在配置服务时使用了这个,并且在将此结构编组为Win32时,我使用了Pack = 1属性将权限字段完全对齐在privilegeCount字段之后。