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?
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With