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
的模拟(通常与DllImportAttribute
和StructLayoutAttribute
),它可以应用于COM接口方法。
尽管如此,我的输出并没有“中国人”。 一切似乎工作正常,我从COM得到正确的Unicode字符。
这是否意味着Char
总是被解释为COM方法互操作的WCHAR
?
我找不到任何确认或否认这一点的文件。
我认为这是一个很好的问题, char
( System.Char
)互操作行为确实值得关注。
在托管代码中, sizeof(char)
始终等于2
(两个字节),因为在.NET中,字符始终是Unicode。
然而,在用于P / Invoke(调用导出的DLL API)和COM(调用COM接口方法)的char
情况下,编组规则不同。
对于P / Invoke , CharSet
可以明确地与任何[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
。 对于ushort
, 0x20AC
将简单地转换为0xAC
。
对于调用COM接口方法 ,故事是完全不同的。 在那里, char
总是被视为表示Unicode字符的双字节值 。 也许,Don Box的“Essential COM”(引用本页的脚注)暗示了这种设计决策的原因:
选择
OLECHAR
类型是为了支持Win32 API使用的通用TCHAR
数据类型,以减少支持每个接口的两个版本(CHAR
和WCHAR
)的需要。 通过仅支持一种字符类型,对象开发人员与其客户端使用的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);