PInvoke, function call with pointer-to-pointer-to-

2019-09-20 03:15发布

问题:

I am creating a new question here as I now know how to ask this question, but I'm still a newb in PInvoke.

I have a C API, with the following structures in it:

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


    typedef struct dpos
    {
        int     id;                       
        char    id2[6];      
        int     num;
        char    code[10];
        char    type[3];
     } DPosT ;

and the following API function:

    int addPos(..., PosT ***posArray,...)

the way I call this in C like this:

    int main(int argc, const char *argv[])
    {
        ...
        PosT  **posArray = NULL;
        ...

        ret_sts = addPos(..., &posArray, ...);
        ...
    }

inside addPos() memory will be allocated to posArray and it will also be populated. allocation is like this using calloc:

    int addPos(..., PosT ***posArray, ...)
    {
         PosT **ptr;
         ...
         *posArray = (PosT **) calloc(size, sizeof(PosT *));
         *ptr = (PosT *)calloc(...);
         ...
         (*posArray)[(*cntr)++] = *ptr;
         ...

         /* Population */
         ...
      }

I have another function that will be called to deallocate that memory.

Now I want to do the same in C#,

I have created this in my C# class:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct DPositionT
    {
       public int Id;

       [MarshalAs(UnmanagedType.LPStr, SizeConst = Constants.Id2Len)]
       public string Id2;

       public int Num;

       [MarshalAs(UnmanagedType.LPStr, SizeConst = Constants.CodeLen)]
       public string Code;

       [MarshalAs(UnmanagedType.LPStr, SizeConst = Constants.TypeLen)]
       public string type;
    }

    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit )]
    struct PosT
    {
        [System.Runtime.InteropServices.FieldOffset(0)]
        DPosT d;
    };

I have only defined d, as I am only going to use this member of the union in my client code.

Now in order to call addPos() how should I create and pass posArray ?

your help is very much appreciated.

回答1:

One of the really important thing to note is that the third * from the PosT*** is there just to provide possibility to return the allocated pointer to the caller. This means that in fact the type is PosT** and that in C# parameter will have to be declared either ref or out modifier.

The fragment you've provided is incomplete, but tells few things:

     *posArray = (PosT **) calloc(size, sizeof(PosT *)); // A

     *ptr = (PosT *)calloc(...);   // B1
     ...
     (*posArray)[(*cntr)++] = *ptr;  // B2

in (A) an array, PosT* is created, then in (B1)+(B2) some cells of that array are initialized to some new arrays of undisclosed size. Please note that with your code snippet, the first 'some' and the second 'some' may be unrelated.

Also, the sizes of those arrays are unknown, except for the top-most "size" that probably comes from parameters.

This means that from C# point of view, the datastructre you want to actually receive is "PosT[][]", so the P/Invoke signature would be like:

[...]
... addPos(..., out PosT[][] posArray, ....)

so, even in C# it would be an 2-dimensional jagged array of returned by parameter, just like in C/C++.

However, unlike C where you can have loose pointers that points to unspecific blocks of data, in C#, every array must have a precisely known length. As the "size" is probably known to you, you can tell the marshaller what is the size of the first dimension.

If the outer "size" is a constant:

[...]
... addPos(..., [MarshalAs(SizeConst=100)]out PosT[][] posArray, ....)

or, if it is passed by parameter:

[...]
... addPos(int size, ...., [MarshalAs(SizeParamIndex=0)]out PosT[][] posArray, ....)

of course assuming that "size" is really the very first parameter.

However, all of this does not specify the size of the inner arrays. What's more, with the code snippet you've provided, those inner arrays may differ in their lengths. Let me play with the code snippet you have presented:

     *posArray = (PosT **) calloc(size, sizeof(PosT *)); // A

     (*cntr) = 0

     for(int idx = 0; idx<size; ++idx)
     {
         *ptr = (PosT *)calloc(5+40*(*cntr));   // B1

         (*posArray)[(*cntr)++] = *ptr;  // B2
     }

Even worse, your snippet does not even show whether the inner arrays are unique or not, so even this is allowed with your snippet:

     *posArray = (PosT **) calloc(size, sizeof(PosT *)); // A

     (*cntr) = 0

     *ptr = (PosT *)calloc(5);   // B1
     *ptr2 = (PosT *)calloc(25);

     for(int idx = 0; idx<size / 2; ++idx)
     {
         (*posArray)[(*cntr)++] = *ptr;  // B2
         (*posArray)[(*cntr)++] = *ptr2;
     }

Note that here I allocate only 2 inner arrays, and I set all cells of the outer array to point to one of those two inner arrays - outer array may have 500 cells, but there are only 2 inner ones. That's also completely correct datastructure and it is possible with your snippet.

In either of those two cases, there is no pretty way of telling the .Net Marshaller about the layout of such data structure. You'd have to obtain the outer array as an array of pointers:

[...]
... addPos(int size, ...., [MarshalAs(SizeParamIndex=0)]out IntPtr[] posArray, ....)

which you can imagine as casting your (PosT**) into (void*) and later then, somewhere in your program, you'd have to manually unpack those various IntPtrs into PosT[] of proper lengths. Of course, you'd have to actually somehow guess what is the correct length.

Here's how to read an array of structs from IntPtr: Getting Array of struct from IntPtr

EDIT:

ah, and I completely forgot that of course, on C# side, you can just obtain the parameter as a raw pointer, so instead of out PosT[][] posarray you can just PosT*** posarray - this one however will require you to add the 'unsafe' modifier to the signature of the "addPos" function on the C# side. Here's some primer on unsafe modifier:

http://www.codeproject.com/Articles/1453/Getting-unsafe-with-pointers-in-C



回答2:

[Update] OK, I have made some progress here,

I couldn't get it to work, when I pass it as out IntPtr[] as when returning from unmanaged code it throws exception. But I came across this link and so I tried to pass it using out IntPtr and that seems to work. at least now I get the pointer back. I now need to start working on Marshalling it all to get what I need out of it.