Parameters passed by reference come back garbage using P/Invoke

前端 未结 2 827
独厮守ぢ
独厮守ぢ 2020-12-22 07:40

I am using Mono/C# on Linux and have the following C# code:

[DllImport(\"libaiousb\")]
extern static ResultCode QueryDeviceInfo(uint deviceIndex, 
    ref ui         


        
相关标签:
2条回答
  • 2020-12-22 07:49

    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.

    0 讨论(0)
  • 2020-12-22 07:56

    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
    
    0 讨论(0)
提交回复
热议问题