【问题标题】:Parameters passed by reference come back garbage using P/Invoke通过引用传递的参数使用 P/Invoke 返回垃圾
【发布时间】:2009-11-18 03:14:45
【问题描述】:

我在 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 参数仍然作为垃圾返回。

有什么想法吗?

【问题讨论】:

  • 它们是原样返回还是作为垃圾返回?
  • 那么你一定是在向变量写入垃圾。我刚刚尝试过,您发布的代码应该可以按预期工作。如果你愿意,我可以发布我的示例代码作为答案。
  • 顺便说一句:如果它解决了您的问题,请不要忘记接受答案。这就是 Stackoverflow.com 说“谢谢”的方式 :-)(单击问题旁边的小复选标记)
  • 我修改了函数,以便更改值然后退出。没有其他的。这是linux代码: // {{{ QueryDeviceInfo() 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,计数器 = %d,名称 = %s",*pPID,*pDIOBytes,*pCounters,pName); *pPID = 9; *pDIOBytes = 8; *pCounters = 7; *pNameSize = 6;返回 AIOUSB_SUCCESS; ref参数还是乱码
  • 如果你能读懂它......它作为评论会变得非常混乱。

标签: c# linux mono pinvoke


【解决方案1】:

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 测试.cs

运行:

$ 单声道测试.exe 100 9 6 8 7 0

【讨论】:

  • 虽然它没有直接解决问题,但它导致了我。我从您的代码 dtb 开始,并从那里扩展。您的代码也可以在我的机器上运行。我正在使用包装函数进行调用,并通过 ref 将所有参数传递给它。这似乎是问题的原因。 Mono 处理得不是很好。如果我为 pinvoke 函数调用使用局部变量,然后在函数调用之后将其分配给包装函数参数,一切都很好。但是我不能使用包装器参数来调用 pinvoked 函数。 (PS我终于看到你说的复选标记了:-))
【解决方案2】:

有点不相关,但要记住的是,C 和 C++ 类型的大小并不是一成不变的。具体来说,sizeof(unsigned long) 在 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);

Is broken -- 它只能在 32 位平台上正常工作(因为 C# uint 始终是 32 位,而 unsigned long 在 LP64 平台上将是 64 位),并且在 64 位平台上会失败(相当可怕)。

有三个修复:

  1. 如果您将始终使用 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 函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-22
    • 2013-11-29
    • 2011-01-30
    • 2010-10-07
    • 2019-09-10
    • 1970-01-01
    相关资源
    最近更新 更多