【问题标题】:Can you PInvoke multibyte ANSI to a varargs? What am I doing wrong?你能 PInvoke 多字节 ANSI 到可变参数吗?我究竟做错了什么?
【发布时间】: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


    【解决方案1】:

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

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

    【讨论】:

    • 我试过这个,但由于某种原因,当我这样做时该功能无效。 (例如,这是 Get/Set 对的“Set”,当我以这种方式调用 Set 时,Get 什么也不返回。)我很确定这是正确的轨道,所以我从那里开始。
    • 实际上在日文机器上使用 Cdecl 时出现 AccessViolationException。好奇。
    猜你喜欢
    • 2013-08-06
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    • 2021-04-26
    • 2019-12-23
    • 2014-06-15
    • 1970-01-01
    • 2011-07-31
    相关资源
    最近更新 更多