C# 编组 C++ 结构继承

C# marshaling C++ struct inheritance

假设我在 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++ 中使用继承时,基本 class 字段是在子结构之前还是之后?

我正在使用 IBase 来强制显示基字段。

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

"appropriate" 一词并不完全适用于这些 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;
}

并使用调试器检查 lenofs 变量,在本例中为 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 变量上逆向处理,您会发现声明错误的成员。请注意使用 属性 的后果,强制检查支持字段的不稳定名称。当使用字段而不是属性声明结构时,此代码将不那么复杂,强烈推荐。

您正在假设 C++ 编译器将如何在内存中布置 class。根据您的编译器和您使用的标志,这可能会改变。也取决于您使用的字段。例如,某些编译器将这样对齐对象是完全合理的:

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

所以您可以看到 C++ classes 可能无法映射到您期望它们在 C# 中的布局方式。

有标志来控制这个——你可以在C++中设置packing为0,然后在C#中使用顺序布局,然后你的做法是合理的。

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

我终于在我尝试做的事情中找到了真正的问题。我从正在使用的 SDK 获得的回调向我发送了 struct Base,通过分析其中的字段,我确定了它是哪种继承类型。然后我必须 "cast" 将基类型转换为继承类型。这是我最初做的事情:

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++ 模型的大小和偏移量。