Convert unmanaged C++ pointer to an object to a managed C# object
Asked Answered
S

1

6

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?

Salpingitis answered 23/3, 2017 at 13:21 Comment(3)
Due to the peculiarities of the implementation my project, there is no way to use C++/CLI.Salpingitis
You are getting the error from the String name. A string in c++ is really a byte[] in c# which is a pointer to an array. So use Intptr name.Fania
You don't need FieldOffset any more once you fix name to be IntPtr.Fania
J
3

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.

Janka answered 23/3, 2017 at 13:33 Comment(6)
Thanks, it's worked for me, but the argument 'unmanagedType' had to be replaced from 'UnmanagedType.LPArray' to 'UnmanagedType.ByValArray' for correct work. With 'UnmanagedType.LPArray' i get exception.Salpingitis
@IvanKishchenko No it doesn't work with ByValArray. Unaccept the response so I can delete it please.Janka
I Unaccept the response as you asked, but it's works for me fine.Salpingitis
@IvanKishchenko It can be random... ByValArray is for when you have struct MyStruct { uint8_t bytes[5]; }Janka
@Ðаn Rarely introducing another project to your solution, in a syntax you don't know (C++/CLI) will make something "easier". While you do probably like very much C++/CLI, you are probably one of the few persons on the earth that like it :-) (otherwise you would find more questions about it on SO). I Consider C++/CLI to be the "nuclear option", not the first line of offense in a pinvoke problem.Janka
@Ðаn, I use Mono for cross-platform. C++/CLI not variant.Salpingitis

© 2022 - 2024 — McMap. All rights reserved.