0

是否可以在 COM 接口的 C# 实现中指定结构打包大小?

(我知道在托管端定义结构时该怎么做,但我的问题是何时在非托管端定义并在托管端实现。)

我有一个 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++ 背景。)

另一方面,如果根本不可能更改托管方的包装,那么我会接受它。

谁能给我一些建议?

4

3 回答 3

3

打包大小由它生成的 RCW 中的 tlbimp.exe 指定。如果我在命令行中传递 /platform:x64,那么 RCW 会显示“.pack 8”,但如果我说 /platform:x86 或 /platform:agnostic,那么它会显示“.pack 4”。

我确实想要 /platform:agnostic 以便我可以在 32 位和 64 位平台上使用相同的 RCW。通常我发现 AnyCPU 麻烦多于它的价值,但这个项目是一个 SDK,如果我可以避免的话,我不想把我对该主题的看法强加给我的用户。

我还想要 8 字节打包,因为 x64 上的 4 字节打包可能很昂贵。请参阅http://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx

我确定的解决方案是反编译 RCW,更改生成的源代码中的 .pack 指令,然后重新编译。

于 2012-03-19T02:15:20.860 回答
2

我们遇到了类似的问题,一些结构在通过 COM 从 C# 传递到 C++ 时,在 32 位 COM 中工作,但在 64 位中被破坏。

我花了一些时间研究,这个线程也很有帮助。

看起来 TlbImp.exe 尝试将每个结构打包为 4 字节对齐,并且只有当它不满足特定条件时,才会将结构打包为 8 字节对齐。它对 32 位和 64 位模式都执行此操作。

但它有一些奇怪的规则来确定它。我发现,对于 x64,如果一个结构只有 4 字节整数、枚举和安全数组,它被打包为 4 字节对齐,否则它被打包为 8 字节对齐。对于 x86,只有当结构中存在 64 位变量时,结构才被打包为 8 字节对齐,否则,它被打包为 4 字节对齐。也许还有其他变体,但这只是我们在产品中发现的。

另一方面,C++ 使用其默认打包(8 字节)打包每个类型,至少对齐 8 字节或成员的大小。

因此,对于 32 位模式,没有问题,因为如果结构中没有 8 字节大小的成员,则 4 字节和 8 字节对齐之间没有区别。但如果有,TlbImp.exe 会将结构打包为 8 字节对齐,因此这也与 C++ 匹配。

然而,对于 64 位模式,存在一种情况,即结构具有 8 字节成员,但被打包为 4 字节对齐。在我们的例子中,它具有 SAFEARRAYs。如果一个结构只有 SAFEARRAYs 和 32 位(或更少?)位整数,它会被打包为 4 字节对齐以用于 x64 平台。但是,由于 SAFEARRAY 是 C++ 端的 64 位指针,因此它的打包方式不同,将 SAFEARRAY 之前的区域填充到 8 字节边距。不知道是bug还是功能。

为了解决这个问题,我们在这种 4 字节对齐的 64 位结构之前注入了 C++ pragma pack 指令。我们通过使用 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 都能正确传递所有结构。

于 2013-03-28T23:47:07.160 回答
2

Pack 属性在这里对你有用吗?这是我自己的代码中的一个示例:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct TOKEN_PRIVILEGES
{
   public int privilegeCount;
   public LUID_AND_ATTRIBUTES privileges;
}

我在配置服务时使用了它,当将此结构编组到 Win32 时,我使用 Pack=1 属性将权限字段完全对齐到 privilegeCount 字段之后。

于 2012-03-13T17:39:15.257 回答