C#编组C ++结构继承

时间:2015-09-15 19:28:53

标签: c# c++ inheritance struct marshalling

让我说我在C ++中有以下结构

struct Base
{
    USHORT  size;
}

struct Inherited : public Base
{
    BYTE    type;
}

我想在C#中编组Inherited,但结构继承在C#中不起作用。正在做以下适当的事情吗?

public interface IBase
{
    ushort Size { get; set; }
}

[StructLayout(LayoutKind.Sequential)]
public struct Inherited : IBase
{
    public ushort Size { get; set; }
    public byte Type { get; set; }
}

我在这里简化了问题,结构更大,难以验证结果。此外,结构来自另一个不太好的文档,使得验证结果更加困难。在C ++中使用继承时,是子结构之前还是之后的基类字段?

我使用IBase作为强制存在基本字段的方法。

不幸的是,我无法控制C ++方面(我集成的外部系统的SDK)。

3 个答案:

答案 0 :(得分:2)

"适当的"并不完全适用于这些C#声明。到目前为止,避免事故的最佳方法是依赖于属性和接口的实现细节。此结构应声明为 internal ,只使用普通字段。

该代码段未演示故障模式,因此我不得不假设它是 出现问题的真实声明的简化版本。检查C#代码是否正确获取结构声明的方法是验证C ++和C#中结构的大小和最后一个字段的偏移量是否相同。首先编写一个小测试程序来检查,这个代码片段的C ++版本应如下所示:

#include <Windows.h>
#include <stddef.h>

struct Base {
    USHORT  size;
};

struct Inherited : public Base {
    BYTE    type;
};


int main()
{
    int len = sizeof(Inherited);
    int ofs = offsetof(Inherited, type);
    return 0;
}

在这种情况下,使用调试器检查 len ofs 变量4和2。在C#中做同样的事情:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        var len = Marshal.SizeOf(typeof(Inherited));
        var ofs = Marshal.OffsetOf(typeof(Inherited), "<Type>k__BackingField");
    }
}
public interface IBase {
    ushort Size { get; set; }
}

[StructLayout(LayoutKind.Sequential)]
public struct Inherited : IBase {
    public ushort Size { get; set; }
    public byte Type { get; set; }
}

仍然是4和2,所以一个完美的匹配和pinvoke应该是好的。当您在真实声明中出现不匹配时,在ofs变量上向后工作,您将发现声明错误的成员。请注意使用属性的后果,强制检查支持字段的名称。当强烈建议使用字段而不是属性声明结构时,此代码将更加复杂。

答案 1 :(得分:1)

您正在假设C ++编译器将如何在内存中布置类。取决于您的编译器和&amp;你正在使用什么标志,这可能会改变。还取决于您使用的字段。例如,一些编译器完全合理地对齐这样的对象:

struct Obj
{
    char c; // <-- starts at 0 byte
    int i;  // <-- starts at 4 byte - 4 byte alignment improves performance.
}

因此,您可以看到C ++类可能无法映射到您希望它们在C#中的布局方式。

有控制它的标志 - 您可以将C ++中的打包设置为0,然后在C#中使用顺序布局,然后您的方法是合理的。

您对属性的使用不是主要问题 - 只要您了解 何处将由编译器为您生成隐式支持字段。

答案 2 :(得分:0)

我终于在我试图做的事情中找到了真正的问题。我在SDK中使用的回调函数发送给我struct Base并通过分析里面的字段确定它是哪种继承类型。然后我必须&#34;演员&#34;基类型到继承类型。这是我最初做的事情:

static T CopyStruct<T>(ref object s1)
{
    GCHandle handle = GCHandle.Alloc(s1, GCHandleType.Pinned);
    T typedStruct = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    handle.Free();
    return typedStruct;
}

这种做法永远不会超出struct Base的大小。因此,Inherited类型的所有额外字段都不能正确初始化(在复制的内存之外读取)。我最终以一种不安全的方式做到如下:

fixed (Base* basePtr= &base)
{
    inherited= *(Inherited*) basePtr;
}

这样,inherited指向原始内存块,可能会在base大小之外读取。

感谢您之前的所有答案!我实际上构建了一个C ++应用程序来验证我所有C ++模型的大小和偏移量。