Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert unmanaged C++ pointer to an object to a managed C# object

I have an unmanaged static library (.dll) written on C++:

// This is a personal academic project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "program.h"

struct MyData
{
    int32_t index;
    char* name;
    //uint8_t* data;
};

extern "C" {
    __declspec(dllexport) MyData* GetMyData()
    {
        MyData* ms = new MyData();
        ms->index = 5;
        ms->name = "Happy string";
        //ms->data = new uint8_t[5] { 4, 8, 16, 32, 64 };
        return ms;
    }
}

The 'GetMyData' method returns pointer to 'MyData' object.

I imported this library into C# projeсt using 'PInvoke' and called the 'GetMyData' method.

// This is a personal academic project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
public class MyData
{
    [FieldOffset(0)]
    public Int32 index;

    [FieldOffset(4)]
    public String name;

    //[FieldOffset(8)]
    //public Byte[] data;
};

class Program
{
    [DllImport("TestCpp.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr GetMyData();

    public static void Main(string[] args)
    {
        // Call 'GetMyData' method and get structure from pointer using marshaling.
        var ptr = GetMyData();
        var ms = Marshal.PtrToStructure<MyData>(ptr);

        // Print my data into console
        var str = ms.index.ToString();
        str += ", " + ms.name;
        //str += ", [" + string.Join(", ", ms.data) + "]";
        Console.WriteLine(str);
        Console.ReadKey();
    }
}

This code works fine, but if I uncomment the using of 'data' member of 'MyData' type (in C++ and C# code), I will get exception in C# code on this line:

var ms = Marshal.PtrToStructure(ptr);

Error: System.Runtime.InteropServices.SafeArrayTypeMismatchException:
'Mismatch has occurred between the runtime type of the array and the sub type recorded in the metadata.'

As I understand the offset argument in the 'FieldOffset' attribute - it's a shift in bytes in unmanaged memory during convert unmanaged C++ object to managed C# object.

Field 'index' has 4 bytes size because it's 32-bit type.

Field 'name' is pointer to char array. For 32-bit architecture it's also 32-bit number (4 bytes).

Which offset in 'FieldOffset' attribute i need to use for 'data' field?

like image 771
Ivan Kishchenko Avatar asked Mar 23 '17 13:03

Ivan Kishchenko


1 Answers

You can't do it easily... As suggested by Ðаn do it manually or

[FieldOffset(8)]
public IntPtr _data;

public byte[] GetData()
{
    // YOU MUST FREE _data C-side! You can't use
    // C++ delete C#-side
    var bytes = new byte[5];
    Marshal.Copy(_data, bytes, 0, bytes.Length);
    return bytes;
}

There is another (little) problem here: I'm against using LayoutKind.Explicit unless you really need it. Start with LayoutKind.Sequential and see if it is enough. Using LayoutKind.Sequential you'll be more ready to switch from 32 to 64 bits, because the struct will be stretched for the size of the pointers.

like image 132
xanatos Avatar answered Oct 05 '22 11:10

xanatos