** 重大更新 ** 我犯了一个小错误,但我仍然对到底发生了什么感到好奇。
我调用的函数实际上是“fooV”,一个带有这个签名的函数:
foo(const char *, const char *, EnumType, va_list)
这清除了我得到的 AccessViolationExceptions,但没有解释为什么 params 参数适用于所有其他 .NET 类型,除了必须转换为多字节 ANSI 字符的字符串。我将让 DLL 的开发人员在参数列表中公开实际使用的版本 ...,如果 va_list 是参数,是否有任何关于 PInvoking 的提示?
** 旧帖 **
这与我最近提出的问题有关,但不同。
我必须使用 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 字符串?
** 更新 ** 这是考虑到当前评论的补充内容。
如果我指定 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() 并传递该指针。这适用于英语和日语。我对不理解为什么这是解决方案感到不舒服,但它确实有效。