通过引用传递的参数使用P / Invoke返回垃圾

时间:2009-11-18 03:14:46

标签: c# linux mono pinvoke

我在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;

所有参数参数仍然以垃圾回复。

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

<强> 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

答案 1 :(得分:1)

有点无关,但需要注意的是C和C ++类型的大小并不是固定不变的。具体来说,sizeof(unsigned long)在32位平台(ILP32系统)上的32位和64位平台(LP64平台)上的64位之间变化。

然后是Win64,这是一个P64平台,所以sizeof(无符号长)== 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 to的ABI中性C绑定,这将使用例如导出函数。 uint64_t(C#ulong)而非导出unsigned long。这将允许您使用C#中的单个ABI(各处64位),要求您提供一个包装C库,它位于您的C#代码和您关心的实际C库之间。 Mono.Posix.dllMonoPosixHelper遵循此路由绑定ANSI C和POSIX函数。