views:

194

answers:

2

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?

+2  A: 

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

Run:

$ mono Test.exe
100
9
6
8
7
0
dtb
While it didn't directly solve the problem it led me to it. I started with your code, dtb, and expanded from there. Your code worked on my machine too.I was using a wrapper function for the call and passing all the parameters by ref to it. That seems to be the cause of the problem. Mono isn't handling that well. If I use a local variable for the pinvoke function call and then after the function call assign it to the wrapper function parameter all is well. But I can't use the wrapper parameter to call the pinvoked function.(PS I finally saw the checkmark you were talking about :-))
jyh
A: 

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:

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

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:

  1. 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.

  2. 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).

  3. 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 exporting unsigned 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 and MonoPosixHelper follow this route to bind ANSI C and POSIX functions.

jonp