通过引用传递的参数使用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正常。 返回值也不错。

但是,通过引用传递的所有其他参数都会返回垃圾。

我修改了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参数仍然作为垃圾回来。

有任何想法吗?

libaiousb.c

 unsigned long QueryDeviceInfo( unsigned long deviceIndex , unsigned long *pPID , unsigned long *pNameSize , char *pName , unsigned long *pDIOBytes , unsigned long *pCounters ) { *pPID = 9; *pDIOBytes = 8; *pCounters = 7; *pNameSize = 6; return 0; } 

libaiousb.so

 gcc -shared -o libaiousb.so libaiousb.c

test.cs中

 using System; using System.Runtime.InteropServices; using System.Text; class Test { [DllImport("libaiousb")] static extern uint QueryDeviceInfo(uint deviceIndex, ref uint pid, ref uint nameSize, StringBuilder name, ref uint dioBytes, ref uint counters); static void Main() { uint deviceIndex = 100; uint pid = 101; uint nameSize = 102; StringBuilder name = new StringBuilder("Hello World"); uint dioBytes = 103; uint counters = 104; uint result = QueryDeviceInfo(deviceIndex, ref pid, ref nameSize, name, ref dioBytes, ref counters); Console.WriteLine(deviceIndex); Console.WriteLine(pid); Console.WriteLine(nameSize); Console.WriteLine(dioBytes); Console.WriteLine(counters); Console.WriteLine(result); } } 

TEST.EXE

 gmcs Test.cs

跑:

 $ mono Test.exe
 100
 9
 6
 8
 7
 0

有些不相干,但要记住的是,C和C ++类型的大小不是固定的。 具体而言,sizeof(无符号长整数)在32位平台(ILP32系统)上的32位和64位平台(LP64平台)上的64位之间有所不同。

然后是Win64,这是一个P64平台,所以sizeof(unsigned long)== 4(32位)。

它的缺点是你的P / Invoke签名:

 [DllImport("libaiousb")] static extern uint QueryDeviceInfo(uint deviceIndex, ref uint pid, ref uint nameSize, StringBuilder name, ref uint dioBytes, ref uint counters); 

坏了 – 它只能在32位平台上正常工作(因为C#uint总是32位, unsigned long在LP64平台上将是64位),并且会在64位平台上失败(相当可怕) 。

有三个修复:

  1. IFF你将永远在Unixy平台上(例如只有ILP32和LP64平台, 而不是 P64 Win64),你可以使用UIntPtr作为unsigned long 。 这将导致它在ILP32平台上是32位,而在LP64平台上是64位 – 这是期望的行为。

  2. 或者,您可以在C#代码中提供多组P / Invoke签名,并执行运行时检查以确定正在运行哪个ABI以确定要使用哪组签名。 您的运行时检查可以使用IntPtr.Size和Environment.OSVersion.Platform来查看您是在Windows(P64)还是Unix(当IntPtr.Size == 4时是ILP32,当IntPtr.Size == 8时是LP64)。

  3. 否则,您需要为P / Invoke提供一个ABI中立的C绑定,使用例如uint64_t (C# ulong )来导出函数,而不是导出unsigned long 。 这将允许您使用来自C#的单个ABI(全球64位), 要求您提供一个位于C#代码和您关心的实际C库之间的包装C库。 Mono.Posix.dllMonoPosixHelper遵循此路线来绑定ANSI C和POSIX函数。