COM方法,Char类型和CharSet

这是我之前的问题的后续问题: .NET互操作是来回复制数组数据,还是固定数组?

我的方法是COM接口方法(而不是DllImport方法)。 C#签名如下所示:

 void Next(ref int pcch, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] char [] pchText); 

MSDN 说 :

当默认情况下具有Unicode格式的托管Char类型传递给非托管代码时,interop编组器会将字符集转换为ANSI。 您可以将DllImportAttribute属性应用于平台调用声明,将StructLayoutAttribute属性应用于COM互操作声明,以控制封送的Char类型使用的字符集。

另外,@ HansPassant在他的回答中说 :

char []不能作为LPWStr封送,它必须是LPArray。 现在CharSet属性起作用,因为你没有指定它,char []将被编组为8位char [],而不是16位wchar_t []。 编组的数组元素大小不同(它不是“blittable”),因此编组器必须复制数组。

非常不受欢迎,特别是考虑到您的C ++代码需要wchar_t。 在这个特定情况下,一个非常简单的方法是在数组中没有得到任何回报。 如果通过复制对数组进行封送处理,则必须明确告诉编组程序在调用后需要将数组复制回来。 您必须在参数上应用[In,Out]属性。 你会得到中国人。

我找不到CharSet的模拟(通常与DllImportAttributeStructLayoutAttribute ),它可以应用于COM接口方法。

尽管如此,我的输出并没有“中国人”。 一切似乎工作正常,我从COM得到正确的Unicode字符。

这是否意味着Char总是被解释为COM方法互操作的WCHAR

我找不到任何确认或否认这一点的文件。

我认为这是一个很好的问题, charSystem.Char )互操作行为确实值得关注。

在托管代码中, sizeof(char)始终等于2 (两个字节),因为在.NET中,字符始终是Unicode。

然而,在用于P / Invoke(调用导出的DLL API)和COM(调用COM接口方法)的char情况下,编组规则不同。

对于P / InvokeCharSet可以明确地与任何[DllImport]属性一起使用,或者通过[module|assembly: DefaultCharSet(CharSet.Auto|Ansi|Unicode)]隐式使用,以更改每个模块的所有[DllImport]声明的默认设置或按assembly。

默认值为CharSet.Ansi ,这意味着将进行Unicode到ANSI转换。 我ussualy用[module: DefaultCharSet(CharSet.Unicode)]将默认值更改为Unicode,然后在我需要调用ANSI API的极少数情况下有选择地使用[DllImport(CharSet = CharSet.Ansi)]

也可以使用MarshalAs(UnmanagedType.U1|U2)MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1|U2) (对于char[]参数)更改任何特定的char -typed参数。 例如,你可能有这样的事情:

 [DllImport("Test.dll", ExactSpelling = true, CharSet = CharSet.Unicode)] static extern bool TestApi( int length, [In, Out, MarshalAs(UnmanagedType.LPArray] char[] buff1, [In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)] char[] buff2); 

在这种情况下, buff1将作为双字节值数组(按原样) buff2 ,但buff2将转换为单字节值数组和从单字节值数组转换。 注意,这仍然是buff2的智能,Unicode到OS当前代码页(和后面)转换。 例如,Unicode’\ x20AC’( )将在非托管代码中变为\x80 (在OS代码页中提供的是Windows-1252 )。 这就是MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)] char[] buff编组与MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)] ushort[] buff 。 对于ushort0x20AC将简单地转换为0xAC

对于调用COM接口方法 ,故事是完全不同的。 在那里, char总是被视为表示Unicode字符的双字节值 。 也许,Don Box的“Essential COM”(引用本页的脚注)暗示了这种设计决策的原因:

选择OLECHAR类型是为了支持Win32 API使用的通用TCHAR数据类型,以减少支持每个接口的两个版本( CHARWCHAR )的需要。 通过仅支持一种字符类型,对象开发人员与其客户端使用的UNICODE预处理器符号的状态分离。

显然,同样的概念已经进入.NET。 即使对于传统的ANSI平台(如Windows 95, Marshal.SystemDefaultCharSize == 1 ),我也非常有信心。

请注意,当DefaultCharSet是COM接口方法签名的一部分时,它对char没有任何影响。 两者都没有明确应用CharSet的方法。 但是,您仍然可以完全控制MarshalAs的每个参数的编组行为,其方式与上面的P / Invoke完全相同。 例如,如果非托管COM代码需要ANSI字符的缓冲区,则Next方法可能如下所示:

 void Next(ref int pcch, [In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 0)] char [] pchText);