Marshal C#类,带有C++的引用成员

Ant*_*rov 5 c# c++ pointers marshalling

我们有一个任务是将一个引用另一个类的类从C#传递给C++.C#类看起来像这样:

[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
}
Run Code Online (Sandbox Code Playgroud)

C++ struct看起来像这样(pack也是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;
};
Run Code Online (Sandbox Code Playgroud)

我们以这样的方式将C#类传递给C++:

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

并在C++中接受它:

extern "C" __declspec(dllexport) int OnFrame(int pId, const FrameInfo *pFrame);
Run Code Online (Sandbox Code Playgroud)

作为FrameInfo结构的结果,我们没有引用而是嵌入类.内存布局如下所示:

0..3 bytes:   uint   frameNumber

4..11 bytes:  double timeStamp

12..15 bytes: uint   width

16..19 bytes: uint height

etc... 
Run Code Online (Sandbox Code Playgroud)

代替:

0..3 bytes:   uint   frameNumber

4..11 bytes:  double timeStamp

12..15 bytes: ImageInfo* image  // pointer to the image
Run Code Online (Sandbox Code Playgroud)

类嵌入会导致额外的数据复制,这让我们非常沮丧.我们试图在MSDN IntPtr的帮助下传递引用,Marshal.StructureToPtr()但MSDN说

"StructureToPtr将结构内容复制到预先分配的内存块"

这样它也可以复制数据.

然后我们尝试在C#中使用C风格的指针:

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

编译器说"不能获取地址,获取大小,或声明指向托管类型的指针"

好.然后我们尝试了一个奇怪的未记录的函数__makeref(),然后将其转换为IntPtr,但它也有相当奇怪的内存布局.

那么,有没有办法像通常的参考一样传递引用?

xan*_*tos 1

可能Pack是错误的。Visual C++ 没有Pack=1,它通常Pack=0...

现在...你可以作弊一点:

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

[DllImport("NativeLibrary", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int OnFrame(int pId, FrameInfo fi);
Run Code Online (Sandbox Code Playgroud)

进而

var fi = new FrameInfo { FrameNumber = 1, TimeStamp = 2, Width = 3, Height = 4, Stride = 5 };

var handle = GCHandle.Alloc(fi, GCHandleType.Pinned);

try
{
    fi.Image = handle.AddrOfPinnedObject() + 2 * sizeof(double) + IntPtr.Size;

    OnFrame(1, fi);
}
finally
{
    handle.Free();
}
Run Code Online (Sandbox Code Playgroud)

请注意我如何将两者“合并”class在一起...... IntPtrnow 指向class.

请注意,IntPtr Image只有在handle.Free()

但此时,我会将所有内容封装在另一种方法中,例如:

[DllImport("NativeLibrary", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int OnFrame(int pId, IntPtr fi);

public static int OnFrameSharp(int pId, FrameInfo fi)
{
    var handle = GCHandle.Alloc(fi, GCHandleType.Pinned);

    try
    {
        IntPtr ptr = handle.AddrOfPinnedObject();
        fi.Image = ptr + 2 * sizeof(double) + IntPtr.Size;

        return OnFrame(pId, ptr);
    }
    finally
    {
        handle.Free();
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,我会提高速度,因为我固定了它,FrameInfo并且 CLR 不需要重新固定它。