0

** 重大更新 ** 我犯了一个小错误,但我仍然对到底发生了什么感到好奇。

我调用的函数实际上是“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() 并传递该指针。这适用于英语和日语。我对不理解为什么这是解决方案感到不舒服,但它确实有效。

4

1 回答 1

2

您可能还需要指定CallingConvention=CallingConvention.Cdecl,因为 varargs 函数将使用_cdecl调用约定(默认为 Winapi,而后者又默认为 StdCall)。

你可能需要别的东西,但我很确定你至少需要那个。

于 2009-05-05T16:54:40.420 回答