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

时间:2015-07-03 15:57:31

标签: 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
}

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;
};

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

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

并在C ++中接受它:

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

结果在FrameInfo结构中,我们没有引用,而是嵌入类。内存布局如下所示:

0..3 bytes:   uint   frameNumber

4..11 bytes:  double timeStamp

12..15 bytes: uint   width

16..19 bytes: uint height

etc... 

而不是:

0..3 bytes:   uint   frameNumber

4..11 bytes:  double timeStamp

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

类嵌入会导致额外的数据复制,这让我们非常沮丧。我们尝试在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;
}

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

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

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

2 个答案:

答案 0 :(得分:2)

  

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

不,由于垃圾收集器,引用不稳定,必须首先固定对象。 pinvoke marshaller拒绝为非平凡的对象图做到这一点。您必须将FrameInfo.Image成员声明为IntPtr并使用GC.Alloc + AddrOfPinnedObject

在这种情况下你可以获得某处,ImageInfo应该是一种结构类型。这允许你声明一个指针:

[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;
}

只要结构存储在堆栈(局部变量或方法参数)上,您就可以轻松初始化FrameInfo.Image:

var info = new ImageInfo { Width = 5, Height = 6, Stride = 7 };
var frame = new FrameInfo { FrameNumber = 1, TimeStamp = 2, Image = &info };
OnFrame(42, frame);

答案 1 :(得分: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);

然后

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();
}

请注意我如何“合并”两个class ... IntPtr现在指向“{1}}内部。

请注意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(); } } 而CLR不需要重新固定它。