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?
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
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:
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 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.