【问题标题】:C# marshaling C++ struct inheritanceC# 封送 C++ 结构继承
【发布时间】:2015-12-12 04:25:28
【问题描述】:

假设我在 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)。

【问题讨论】:

  • 我认为期望基本结构在 C++ 内存布局中排在第一位并不是不合理的,就好像它是第一个成员一样(但我不认为它是标准化的)。你试过了吗?
  • @TheodorosChatzigiannakis 是的,我当然尝试并运行了代码,但正如我所说:“[...]我的结构更大,因此很难验证结果。”这就是为什么我试图验证我的假设。感谢您的输入!
  • 您可能应该使用字段而不是属性,但我不知道这是否可行。
  • 我还建议您使用字段而不是自动属性。是的,至少只要您坚持使用 MSVC(或任何与原始 Windows SDK 兼容的编译器),您就可以假定基类字段直接位于派生类的字段之前。
  • 如果你打算与 C++ 进行大量互操作,你可以看看 C++/CLI

标签: c# c++ inheritance struct marshalling


【解决方案1】:

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

sn-p 没有展示故障模式,所以我不得不假设它是 确实 有问题的真实声明的简化版本。检查 C# 代码是否正确获取结构声明的方法是验证结构的大小和最后一个字段的偏移量在 C++ 和 C# 中是否相同。首先编写一个小测试程序来检查,这个 sn-p 的 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 变量上向后工作,你会发现被声明错误的成员。请注意使用该属性的后果,强制检查支持字段的可疑名称。当使用字段而不是属性来声明结构时,此代码将大大减少复杂性,强烈推荐。

【讨论】:

    【解决方案2】:

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

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

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

    有一些标志可以控制这一点 - 你可以在 C++ 中将包装设置为 0,然后在 C# 中使用顺序布局,然后你的方法是合理的。

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 2014-12-22
        • 1970-01-01
        • 1970-01-01
        • 2011-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-26
        • 1970-01-01
        相关资源
        最近更新 更多