Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return array of pointers from c++ to c#

Tags:

c++

c#

pinvoke

Below I have a code snippet from c++. I need to return array of pointers (to TempStruct).

The problem is that on c# side I get only one element. On the other I get AV.

**C++**
extern "C" __declspec(dllexport) void GetResult(TempStruct** outPtr, long *size)
{       
    *outPtr = (TempStruct*)new TempStruct*[2];

     outPtr[0] = new TempStruct("sdf", 123);
     outPtr[1] = new TempStruct("abc", 456);

    *size = 2;      
}

**C#**    
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetResult(out IntPtr outPtr, out int size);

IntPtr ptr = IntPtr.Zero;
int length;        
GetResult(out ptr, out length);

int size = Marshal.SizeOf(typeof(TempStruct));
TempStruct[] someData2 = new TempStruct[length];

for (int i = 0; i < length; i++)
{
   IntPtr wskptr = (IntPtr)(ptr.ToInt64() + (size * i));
   someData2[i] = (TempStruct)Marshal.PtrToStructure(wskptr, typeof(TempStruct));            
} 
like image 962
John Avatar asked Mar 15 '23 15:03

John


2 Answers

You are doing it wrong.

You are mixing pointer types.

By using the new TempStruct() you are creating an array of pointers to TempStruct. The example I gave you created an array of TempStruct. See the difference?

Now... TempStruct** outPtr should be TempStruct*** outPtr (because you want to return (*) an array (*) of pointers (*)... Or TempStruct**& if you prefer :-)

Change this line

someData2[i] = (TempStruct)Marshal.PtrToStructure(Marshal.ReadIntPtr(wskptr), typeof(TempStruct));

Because you must read the single pointers.

I do hope you are deleting the various TempStruct with delete and using the

delete[] ptr;

operator to delete the array of structures.

Full example:

C++:

struct TempStruct
{
    char* str;
    int num;

    // Note the strdup. You don't know the source of str.
    // For example if the source is "Foo", then you can't free it.
    // Using strdup solves this problem.
    TempStruct(const char *str, int num) 
        : str(strdup(str)), num(num)
    {
    }

    ~TempStruct()
    {
        free(str);
    }
};

extern "C"
{
    __declspec(dllexport) void GetResult(TempStruct ***outPtr, int *size)
    {
        *outPtr = new TempStruct*[2];

        (*outPtr)[0] = new TempStruct("sdf", 123);
        (*outPtr)[1] = new TempStruct("abc", 456);

        *size = 2;
    }

    __declspec(dllexport) void FreeSomeData(TempStruct **ptr, int size)
    {
        for (int i = 0; i < size; i++)
        {
            delete ptr[i];
        }

        delete[] ptr;
    }
}

C#:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
    public string str;
    public int num;
}

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResult(out IntPtr outPtr, out int numPtr);

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeSomeData(IntPtr ptr, int num);

// C++ will return its TempStruct array in ptr
IntPtr ptr;
int size;

GetResult(out ptr, out size);

TempStruct[] someData2 = new TempStruct[size];

for (int i = 0; i < size; i++)
{
    IntPtr ptr2 = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
    someData2[i] = (TempStruct)Marshal.PtrToStructure(ptr2, typeof(TempStruct));
}

// Important! We free the TempStruct allocated by C++. We let the
// C++ do it, because it knows how to do it.
FreeSomeData(ptr, size);

Note that you don't need [Serializable] and Pack=1 on the C# struct

More correct for the C++:

__declspec(dllexport) void GetResult(TempStruct **&outPtr, int &size)
{
    outPtr = new TempStruct*[2];

    outPtr[0] = new TempStruct("sdf", 123);
    outPtr[1] = new TempStruct("abc", 456);

    size = 2;
}

It is more correct because both outPtr and size can't be NULL. See https://stackoverflow.com/a/620634/613130 . The C# signature is the same.

like image 145
xanatos Avatar answered Mar 18 '23 15:03

xanatos


The C++ code is wrong. It's returning an array of pointers to struct. The fact that you cast the value returned by new should have alerted you to the fact that you made a mistake. You want to return an array of struct.

It should be:

*outPtr = new TempStruct[2];
(*outPtr)[0].str = "sdf";
(*outPtr)[0].i = 123;
(*outPtr)[1].str = "abc";
(*outPtr)[1].i = 456;
*size = 2;      
like image 25
David Heffernan Avatar answered Mar 18 '23 14:03

David Heffernan