您可以将多字节 ANSI PInvoke 转换为可变参数吗? 我究竟做错了什么?
** 主要更新 ** 我犯了一个小错误,但我仍然很好奇究竟发生了什么。
我调用的函数实际上是“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() 并传递该指针。 这适用于英语和日语。 我不明白为什么这是解决方案,但它确实有效。
** MAJOR UPDATE **
I made a minor mistake but I'm still curious about exactly what is happening.
The function I am calling is actually "fooV", a function with this signature:
foo(const char *, const char *, EnumType, va_list)
This clears up the AccessViolationExceptions I was getting, but doesn't explain why params parameters work for every other .NET type except for strings that will have to be converted to multibyte ANSI characters. I'm going to get the developer of the DLL to expose the version that actually uses ... in the parameter list, any hints for PInvoking if va_list is the parameter?
** old post **
This is related to, but different from a recent question I asked.
I have to use PInvoke to call a C library function that has the following signature:
foo(const char *, const char *, EnumType, ...)
The function configures a resource in a way that varies by StructType; the case I'm interested in configures something that expects the varargs to be a single ANSI multibyte string. I used this PInvoke signature to call the function:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo")]
public static extern int Foo(string s1, string s2, EnumType st1, params string[] s3);
The params string[]
seemed odd to me, but previous signatures that called this function were using it for other types such as bool
so I followed the pattern.
I wrap this with a friendlier .NET method:
public void Foo(string s1, string s2, string s3)
{
int error = Dll.Foo(s1, s2, EnumType.Foo, s3);
// handle errors
}
Recently I changed the signature to include "BestFitMapping = false, ThrowOnUnmappableChar = true" in the DLLImport attribute to comply with FxCop's suggestions. This is a red herring as I will describe later.
What I expect out of making this change is limited support for Unicode. For example, on a machine with an English code page an exception will be thrown if you pass a string that contains a Japanese character. On a Japanese machine, I'd expect to be able to pass a Japanese string to the function. The English test worked as expected, but the Japanese test throws a System.Runtime.InteropServices.COMException with HRESULT 0x8007007A. This happens even without the BestFitMapping and ThrowOnUnmappableChar settings.
I've done a little looking around and saw some sites that suggested you could PInvoke varargs by just specifying normal arguments, so I tried this signature:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo")]
public static extern int Foo(string s1, string s2, EnumType st1, string s3);
This throws an AccessViolationException when I use it on either the English or Japanese machine.
I cannot use UTF-8 or another Unicode encoding because this C library expects and handles only multibyte ANSI. I have found that specifying CharSet.Unicode for this function works, but I'm worried it's only by coincidence and not something upon which I should rely. I have thought about converting the string to a byte[] using the system code page, but can't figure out how to specify I'd like to pass a byte array to the varargs parameter.
What's going on? On the English machine, English characters work fine and Japanese charactes throw an ArgumentException as expected. On the Japanese machine, English characters work fine and Japanese characters throw the COMException. Is there something wrong with my PInvoke signature? I've tried using the MarshalAs
attribute to specify a type of LPArray and subtype of LPStr, but this fails in the same way. UnmanagedType.LPStr indicates it is a single-byte ANSI string; is there a way to specify a multibyte ANSI string?
** UPDATE **
Here's an addition of stuff taking current comments into account.
If I specify a calling convention of CDecl and take normal parameters like this:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", CallingConvention = CallingConvention.Cdecl)]
public static extern int Foo(string s1, string s2, EnumType e1, string s3)
When I use this specification, I get an AccessViolationException. So I tried this:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", CallingConvention = CallingConvention.CDecl)]
public static extern int Foo(string s1, string s2, EnumType e1, string s3)
This works for English and throws the COMException when I use Japanese characters.
The only thing I've found that works consistently is this:
[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)
To make this work, I use Marshal.StringToHGlobalAnsi() and pass that pointer. This works for English and Japanese. I'm not comfortable with not understanding why this is the solution, but it works.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可能还需要指定 CallingConvention=CallingConvention.Cdecl ,因为 varargs 函数将使用 _cdecl 调用约定(默认为 Winapi,而 Winapi 又默认为 StdCall)。
您可能还需要其他东西,但我确信您至少需要那个。
You probably also need to specify
CallingConvention=CallingConvention.Cdecl
since a varargs function will use a_cdecl
calling convention (the default is Winapi which in turn defaults to StdCall).You may need something else, but I'm pretty sure you'll need at least that.