Pinvoke- to call a function with pointer to pointe

2019-07-19 07:03发布

问题:

I have a function in C with this signature:

int addPos(int init_array_size, 
           int *cnt, 
           int *array_size, 
           PosT ***posArray, 
           char *infoMsg);

and here is what PosT looks like:

typedef union pu
{
    struct  dpos   d;
    struct  epo    e;
    struct  bpos   b;
    struct  spos   c;
} PosT ;

What's the best way to call this method in C# via P/Invoke? Do I need to define a class in C# representing PosT? How do I pass PosT ***posArray parameter across from C# to C?

回答1:

You have described how the PosT looks like, but this is not enough. First, have to know what the function expects to be passed as the ***PosT argument, and only THEN you can think of invoking it from C++ or C# side.

I know that probably does not fit your wishes, but please look:

PosT p;
PosT* ptr = &p;
PosT** ptr2 = &ptr;
PosT*** ptr3 = &ptr2;
func(...., ptr3, ...); // OK!?


PosT* ptr = new PosT[123];
PosT** ptr2 = &ptr;
PosT*** ptr3 = &ptr2;
func(...., ptr3, ...); // OK!?


PosT** ptr2 = new PosT[5];
for(int i=0;i<5;++i) ptr2[i] = new PosT[123];
PosT*** ptr3 = &ptr2;
func(...., ptr3, ...); // OK!??

And so on. Which one in-memory structure that I have quickly built is correct for that function? This is what determines the datatype that you will have to pass from the C# side.

If the function takes a thing you'd call a "mutable reference to jagged aray" (so the LAST example I provided), so the P/Invoke declaration would be:

[..extern..]
void func(....., ref PosT[][] posArray, ...);

invoked similar to:

func(...., new PosT[][] { 
         new PosT[] { new PosT{ d = ... }, new PosT{ d = ... }, ... },
         new PosT[] { new PosT{ d = ... }, new PosT{ d = ... }, ... },
         ... },
    .... );

but, please, first check what this function expects. With * there are really too many posibilities to just guess. You say it is from some API - check in its docs first! Tell us what exactly this function expects and me/someone will tell you how to build such POD in C#. Other way round it will not work! :)

PS. sorry for crappy C++/C# code, I'm in haste and only had a few minutes to write this:/



回答2:

Question #1 do I need to define a class in CSharp representing PosT?
You should define PosT as a struct in c#. Since this a union struct you will need to apply the StructLayout attribute, and fully define your other structs. It might look something like this:

struct  dpos { };
struct  epo  { };
struct  bpos { };
struct  spos { };
[System.Runtime.InteropServices.StructLayout(
    System.Runtime.InteropServices.LayoutKind.Explicit, Size=99)]
struct PosT {
    [System.Runtime.InteropServices.FieldOffset(0)] dpos   d;
    [System.Runtime.InteropServices.FieldOffset(0)] epo    e;
    [System.Runtime.InteropServices.FieldOffset(0)] bpos   b;
    [System.Runtime.InteropServices.FieldOffset(0)] spos   c;
    };

** Note that the "Size=99" isn't correct. Ideally, you should set this size to the number of bytes used by the largest enclosed struct.

Question #2- how do I pass PosT ***posArray parameter across frm CSharp to C?
You have to be really careful doing this sort of thing, especially if you expect to allocate the memory for posArray under C#. You don't want to pass a buffer to C that the .NET GC is about to move/change. If C is returning the buffer you should be ok (but you will leak memory if there isn't a C function to let you release the memory). You should take a look at Default Marshaling for Arrays and www.pinvoke.net might have some examples. If you are sending the posArray buffer from C# to C you are going to have to resort to an unsafe context (see below). I'm not sure exactly how to handle that level or redirection. Your cnt and array_size can be handled using the 'ref' keyword.

Question #3- How do I specify marshaling for it all?
Take a look at the links above especially the Default Marshaling for Arrays. You might also need to just play about with some test code, break after the call and use the immediate window to investigate the structures.


Remember

You can pretty much do a straight (c-like) call if you compile c# using the "UNSAFE" switch. For example:

unsafe public MainWindow() {
    InitializeComponent();
    int abc = 4;
    int* abcPtr = &abc;
    *abcPtr = 8;

    fixed (PosT* gog = new PosT[30]) {
        PosT* gogPtr = (gog + 1);
        }
    }

You have to be very careful because C# manages it's own memory and can move things on you (see 'fixed' above). I'm not recommending this but it's useful for quick and dirty sorts of things

Also if the API is defined in a tlb (table library) visual studio will usually offer to create the bindings for you via a stub dll (see tlbimp command)


Hopefully, someone can provide a more specific response but I think this info could at least start you down the road.