使用 P/Invoke 通过引用传递的参数返回垃圾
我在 Linux 上使用 Mono/C# 并具有以下 C# 代码:
[DllImport("libaiousb")]
extern static ResultCode QueryDeviceInfo(uint deviceIndex,
ref uint PID, ref uint nameSize, StringBuilder name,
ref uint DIOBytes, ref uint counters);
我调用了一个 Linux 共享库调用,定义如下:
unsigned long QueryDeviceInfo(
unsigned long DeviceIndex
, unsigned long *pPID
, unsigned long *pNameSize
, char *pName
, unsigned long *pDIOBytes
, unsigned long *pCounters
)
在调用 Linux 函数之前,我已将参数设置为已知值。我还在 Linux 函数的开头放置了一个 printf,并且所有参数都按预期打印值。所以参数似乎从C#传递到Linux就ok了。回报率也不错。
但是,通过引用传递的所有其他参数都会返回垃圾。
我修改了 Linux 函数,因此它只是修改值并返回。代码如下:
unsigned long QueryDeviceInfo(
unsigned long DeviceIndex
, unsigned long *pPID
, unsigned long *pNameSize
, char *pName
, unsigned long *pDIOBytes
, unsigned long *pCounters
) {
printf ("PID = %d, DIOBYtes = %d, Counters = %d, Name= %s", *pPID, *pDIOBytes, *pCounters, pName);
*pPID = 9;
*pDIOBytes = 8;
*pCounters = 7;
*pNameSize = 6;
return AIOUSB_SUCCESS;
所有 ref 参数仍然返回为垃圾。
有什么想法吗?
I am using Mono/C# on Linux and have the following C# code:
[DllImport("libaiousb")]
extern static ResultCode QueryDeviceInfo(uint deviceIndex,
ref uint PID, ref uint nameSize, StringBuilder name,
ref uint DIOBytes, ref uint counters);
And I call a Linux shared library call defined as follows:
unsigned long QueryDeviceInfo(
unsigned long DeviceIndex
, unsigned long *pPID
, unsigned long *pNameSize
, char *pName
, unsigned long *pDIOBytes
, unsigned long *pCounters
)
I have set the parameters to known values before calling the Linux function. I've also put a printf at the beginning of the Linux function and all the parameters are printing values as expected. So the parameters seem to be passed from C# to Linux ok. The return value is also good.
However, all the other parameters that are passed by reference come back garbage.
I modified the Linux function so it simply modifies the values and returns. Here's that code:
unsigned long QueryDeviceInfo(
unsigned long DeviceIndex
, unsigned long *pPID
, unsigned long *pNameSize
, char *pName
, unsigned long *pDIOBytes
, unsigned long *pCounters
) {
printf ("PID = %d, DIOBYtes = %d, Counters = %d, Name= %s", *pPID, *pDIOBytes, *pCounters, pName);
*pPID = 9;
*pDIOBytes = 8;
*pCounters = 7;
*pNameSize = 6;
return AIOUSB_SUCCESS;
All the ref parameters still come back as garbage.
Any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
libaiousb.c
libaiousb.so
Test.cs
Test.exe
运行:
libaiousb.c
libaiousb.so
Test.cs
Test.exe
Run:
有点不相关,但要记住的是,C 和 C++ 类型的大小并不是一成不变的。具体来说,sizeof(unsigned long) 在 32 位平台(ILP32 系统)上的 32 位和 64 位平台(LP64 平台)上的 64 位之间会有所不同。
然后是Win64,它是P64平台,所以sizeof(unsigned long) == 4(32位)。
缺点是您的 P/Invoke 签名:
已损坏——它只能在 32 位平台上正常工作(因为 C# uint 始终是 32 位,而
unsigned long
在 LP64 平台上将是 64 位),并且在 64 位平台上将失败(相当可怕)。共有三个修复:
IFF,您将始终在 Unixy 平台上(例如,仅限 ILP32 和 LP64 平台,不是 P64 Win64),您可以使用 UIntPtr 表示
unsigned long
。这将导致它在 ILP32 平台上为 32 位,在 LP64 平台上为 64 位——这是所需的行为。或者,您可以在 C# 代码中提供多组 P/Invoke 签名,并执行运行时检查以确定您正在运行的 ABI,从而确定要使用哪组签名。您的运行时检查可以使用 IntPtr.Size 和 Environment.OSVersion.Platform 来查看您是否在 Windows (P64) 或 Unix 上(当 IntPtr.Size == 4 时为 ILP32,当 IntPtr.Size == 8 时为 LP64)。
否则,您需要为 P/Invoke 提供 ABI 中性 C 绑定,这将使用例如
uint64_t
(C#ulong
) 导出函数,而不是导出 <代码>无符号长。这将允许您使用 C# 中的单个 ABI(到处都是 64 位),但是要求您提供一个位于 C# 代码和您关心的实际 C 库之间的包装 C 库。Mono.Posix.dll
和MonoPosixHelper
按照此路线绑定 ANSI C 和 POSIX 函数。Somewhat unrelated, but something to keep in mind is that the sizes of C and C++ types are not fixed in stone. Specifically, sizeof(unsigned long) will vary between 32-bits on 32-bit platforms (ILP32 systems) and 64-bits on 64-bit platforms (LP64 platforms).
Then there's Win64, which is a P64 platform, so sizeof(unsigned long) == 4 (32 bits).
The short of it is that your P/Invoke signature:
Is broken -- it will only work correctly on 32-bit platforms (because C# uint is always 32-bits, while the
unsigned long
will be 64-bits on LP64 platforms), and will FAIL (rather horribly) on 64-bit platforms.There are three fixes:
IFF you will always be on Unixy platforms (e.g. ILP32 and LP64 platforms only, not P64 Win64), you can use UIntPtr for
unsigned long
. This will cause it to be 32-bits on ILP32 platforms, and 64-bits on LP64 platforms -- the desired behavior.Alternatively, you can provide multiple sets of P/Invoke signatures in your C# code, and perform a runtime check to determine which ABI you're running on to determine which set of signatures to use. Your runtime check could use IntPtr.Size and Environment.OSVersion.Platform to see if you're on Windows (P64) or Unix (ILP32 when IntPtr.Size == 4, LP64 when IntPtr.Size == 8).
Otherwise, you need to provide an ABI-neutral C binding to P/Invoke to, which would export functions using e.g.
uint64_t
(C#ulong
) instead of exportingunsigned long
. This would allow you to use a single ABI from C# (64-bits everywhere), but requires that you provide a wrapping C library that sits between your C# code and the actual C library you care about.Mono.Posix.dll
andMonoPosixHelper
follow this route to bind ANSI C and POSIX functions.