Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshal C# class with reference members to C++

We have a task to pass a class with a reference to another class from C# to C++. C# class looks like this:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class ImageInfo
{
    public uint Width;
    public uint Height;
    public uint Stride;
}

[StructLayout(LayoutKind.Sequential, Pack=1)]
internal class FrameInfo
{
    public int FrameNumber;
    public double TimeStamp;
    public ImageInfo Image; // This is the problem
}

C++ struct looks like this (pack is also 1):

struct FrameInfo
{
public:

    unsigned int    frameNumber;
    double          timeStamp;
    ImageInfo       *image;
};

struct ImageInfo
{
public:

    unsigned int     m_width;
    unsigned int     m_height;
    unsigned int     m_stride;
};

We pass the C# class to C++ in such a way:

[DllImport("ourLib.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int OnFrame(int pId, FrameInfo pFrame);

And accept it in C++:

extern "C" __declspec(dllexport) int OnFrame(int pId, const FrameInfo *pFrame);

As a result in the FrameInfo struct we have not a reference but an embedded class. Memory layout looks like this:

0..3 bytes:   uint   frameNumber

4..11 bytes:  double timeStamp

12..15 bytes: uint   width

16..19 bytes: uint height

etc... 

instead of:

0..3 bytes:   uint   frameNumber

4..11 bytes:  double timeStamp

12..15 bytes: ImageInfo* image  // pointer to the image

Class embedding causes an additional data copying that makes us very upset. We tried to pass the reference as IntPtr with the help of Marshal.StructureToPtr() but MSDN says that

"StructureToPtr copies the contents of structure to the pre-allocated block of memory"

so that it also copies the data.

Then we tried to use C-style pointers in C#:

[StructLayout(LayoutKind.Sequential, Pack=1)]
internal unsafe class FrameInfo
{
    public int FrameNumber;
    public double TimeStamp;
    public ImageInfo *Image;
}

Compiler says that "Cannot take the address of, get the size of, or declare a pointer to a managed type"

OK. Then we tried a strange not-documented function __makeref() and then casting it to the IntPtr but it also has rather strange memory layout.

So, is there any way to pass the reference just as usual reference?

like image 751
Anton Egorov Avatar asked Jul 03 '15 15:07

Anton Egorov


1 Answers

So, is there any way to pass the reference just as usual reference?

No, references are not stable due to the garbage collector, an object has to be pinned first. The pinvoke marshaller refuses to do so for non-trivial object graphs. You'd have to declare the FrameInfo.Image member as IntPtr and use GC.Alloc + AddrOfPinnedObject

You can get somewhere in this case, ImageInfo ought to be a struct type. Which allows you declare a pointer:

[StructLayout(LayoutKind.Sequential)]
public struct ImageInfo {
    public uint Width;
    public uint Height;
    public uint Stride;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe class FrameInfo {
    public int FrameNumber;
    public double TimeStamp;        
    public ImageInfo* Image;
}

And you can easily initialize FrameInfo.Image as long as the struct is stored on the stack (local variable or method argument):

var info = new ImageInfo { Width = 5, Height = 6, Stride = 7 };
var frame = new FrameInfo { FrameNumber = 1, TimeStamp = 2, Image = &info };
OnFrame(42, frame);
like image 117
Hans Passant Avatar answered Nov 10 '22 06:11

Hans Passant