【发布时间】:2010-10-23 23:23:32
【问题描述】:
** 重大更新 ** 我犯了一个小错误,但我仍然对到底发生了什么感到好奇。
我调用的函数实际上是“fooV”,一个带有这个签名的函数:
foo(const char *, const char *, EnumType, va_list)
这清除了我得到的 AccessViolationExceptions,但没有解释为什么 params 参数适用于所有其他 .NET 类型,除了必须转换为多字节 ANSI 字符的字符串。我要让 DLL 的开发人员在参数列表中公开实际使用的版本...,如果 va_list 是参数,PInvoking 的任何提示?
** 旧帖**
这与recent question I asked 相关但不同。
我必须使用 PInvoke 调用具有以下签名的 C 库函数:
foo(const char *, const char *, EnumType, ...)
该函数配置资源的方式因 StructType 而异;我感兴趣的情况配置了一些期望可变参数是单个 ANSI 多字节字符串的东西。我使用这个 PInvoke 签名来调用函数:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo")]
public static extern int Foo(string s1, string s2, EnumType st1, params string[] s3);
params string[] 对我来说似乎很奇怪,但之前调用此函数的签名将它用于其他类型,例如 bool,所以我遵循了这个模式。
我用一种更友好的 .NET 方法来包装它:
public void Foo(string s1, string s2, string s3)
{
int error = Dll.Foo(s1, s2, EnumType.Foo, s3);
// handle errors
}
最近我将签名更改为在 DLLImport 属性中包含“BestFitMapping = false, ThrowOnUnmappableChar = true”以符合 FxCop 的建议。这是一条红鲱鱼,我稍后会描述。
我对进行此更改的期望是对 Unicode 的有限支持。例如,在具有英文代码页的机器上,如果您传递包含日文字符的字符串,则会引发异常。在日文机器上,我希望能够将日文字符串传递给函数。英语测试按预期工作,但日语测试抛出 System.Runtime.InteropServices.COMException,HRESULT 为 0x8007007A。即使没有 BestFitMapping 和 ThrowOnUnmappableChar 设置也会发生这种情况。
我环顾四周,看到一些网站建议您只需指定普通参数即可 PInvoke varargs,所以我尝试了这个签名:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo")]
public static extern int Foo(string s1, string s2, EnumType st1, string s3);
当我在英语或日语机器上使用它时,这会引发 AccessViolationException。
我不能使用 UTF-8 或其他 Unicode 编码,因为这个 C 库只需要并处理多字节 ANSI。我发现为这个函数指定 CharSet.Unicode 是可行的,但我担心这只是巧合,而不是我应该依赖的东西。我曾考虑使用系统代码页将字符串转换为 byte[],但不知道如何指定我想将字节数组传递给 varargs 参数。
发生了什么事?在英文机器上,英文字符工作正常,日文字符按预期抛出 ArgumentException。在日文机器上,英文字符工作正常,日文字符抛出 COMException。我的 PInvoke 签名有问题吗?我尝试使用MarshalAs 属性来指定LPArray 的类型和LPStr 的子类型,但这同样失败。 UnmanagedType.LPStr 表示它是一个单字节的 ANSI 字符串;有没有办法指定多字节 ANSI 字符串?
** 更新 ** 这是将当前 cmets 考虑在内的附加内容。
如果我指定 CDecl 的调用约定并采用这样的普通参数:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", CallingConvention = CallingConvention.Cdecl)]
public static extern int Foo(string s1, string s2, EnumType e1, string s3)
当我使用这个规范时,我得到了一个 AccessViolationException。所以我尝试了这个:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", CallingConvention = CallingConvention.CDecl)]
public static extern int Foo(string s1, string s2, EnumType e1, string s3)
这适用于英语,当我使用日语字符时会抛出 COMException。
我发现唯一能始终如一地工作的是:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern int Foo(string s1, string s2, EnumType e1, params IntPtr[] s3)
为了完成这项工作,我使用 Marshal.StringToHGlobalAnsi() 并传递该指针。这适用于英语和日语。我不明白为什么这是解决方案,但它确实有效。
【问题讨论】:
标签: string internationalization pinvoke variadic-functions