2015-07-03 115 views
5

我们有一个任务可以将一个类的引用传递给另一个从C#到C++的类。 C#类看起来是这样的:Marshal 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++结构看起来像这样(包也是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); 

作为一个资源ult在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 

类嵌入导致额外的数据复制,使我们很不高兴。我们试图通过引用作为IntPtrMarshal.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,但它也具有相当奇怪的内存布局。

那么,有没有什么办法像往常一样引用参考?

+0

你试过从'MarshalByRefObject'派生'ImageInfo'吗? –

回答

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现在点”内“class

注意,IntPtr Image将是“正确的”只有等到handle.Free()

但在这一点上,我会在另一种方法incapsulate一切,如:

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

这样,我会有所收获在速度上,因为我销售FrameInfo和CLR不必重新钉住它。

+0

是的,这很好!虽然我相信'2 * sizeof(double)'应该是'sizeof(int)+ sizeof(double)' –

+0

@AntonEgorov否,'double'通常在8字节边界对齐,所以如果它们需要填充前面有一个'int' – xanatos

2

那么,有没有什么办法像往常一样引用参考?

不,由于垃圾收集器引用不稳定,必须首先锁定对象。 pinvoke编组拒绝为非平凡的对象图做到这一点。你必须申报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。图像,只要该结构被存储在栈(局部变量或方法参数)上:

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