简体   繁体   English

C#封送处理C ++结构继承

[英]C# marshaling C++ struct inheritance

Let's say I have the following structs in C++ 假设我在C ++中具有以下结构

struct Base
{
    USHORT  size;
}

struct Inherited : public Base
{
    BYTE    type;
}

I want to marshal Inherited in C# but the struct inheritance does not work in C#. 我想编组C#中的Inherited ,但结构继承在C#中不起作用。 Is doing the following appropriate ? 做下面的适当吗?

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

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

I simplified the problem here and my structs are way bigger making it difficult to validate the results. 我在这里简化了问题,而且结构更大,难以验证结果。 Also, the structs are coming from another software that is not so well document making it even harder to validate the results. 而且,结构来自另一个软件,该软件的文档不够完善,因此很难验证结果。 When using inheritance in C++, are the base class fields before or after the child struct ? 在C ++中使用继承时,基类字段在子struct之前还是之后?

I'm using the IBase as a way to enforce the base fields to be present. 我正在使用IBase作为强制存在基本字段的方法。

Unfortunately, I don't have control over the C++ side (SDK for an external system I integrate with). 不幸的是,我无法控制C ++方面(集成了外部系统的SDK)。

The word "appropriate" does not exactly apply to these C# declarations. 单词“适当的”并不完全适用于这些C#声明。 By far the best way to avoid accidents is by not relying on implementation details of properties and interfaces. 到目前为止,避免事故的最佳方法是依赖属性和接口的实现细节。 This struct should be declared internal and just use plain fields. 该结构应声明为内部结构,并且仅使用纯字段。

The snippet does not demonstrate a failure mode so I'll have to assume that it is a simplified version of the real declaration that does have a problem. 该代码段未演示失败模式,因此我必须假定它是确实存在问题的真实声明的简化版本。 The way to check that C# code gets the structure declaration right is to verify that the size of the structure and the offset of the last field are the same in both C++ and C#. 检查C#代码是否正确声明结构的方法是验证C ++和C#中结构的大小和最后一个字段的偏移量是否相同。 Start by writing a little test program to check that, the C++ version for this snippet should look like this: 首先编写一个测试程序来检查该片段的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;
}

And use the debugger to inspect the len and ofs variables, 4 and 2 in this case. 并使用调试器检查lenofs变量,在这种情况下为4和2。 Do the exact same thing in C#: 在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; }
}

Still 4 and 2, so a perfect match and pinvoke should be good. 仍然是4和2,所以完美的搭配和点赞应该很好。 When you get a mismatch on the real declaration, work your way backwards on the ofs variable, you'll discover the member that was declared wrong. 当您在真实声明中出现不匹配时,对ofs变量进行反向操作,您将发现被声明为错误的成员。 Do note the consequence of using the property, forcing to check on wonky name of the backing field. 请注意使用该属性的结果,强制检查后备字段的名称不正确。 This code will be much less convoluted when the struct is declared by using fields instead of properties, strongly recommended. 强烈建议通过使用字段而不是属性来声明该结构时,使该代码的处理复杂得多。

You are making assumptions of how the C++ compiler will lay out the class in memory. 您正在假设C ++编译器将如何在内存中布置该类。 Depending on your compiler & what flags you are using, this may change. 根据您的编译器和使用的标志,这可能会改变。 Also depending on the fields you are using. 还取决于您使用的字段。 For example, some compilers would be perfectly reasonable to align an object like this: 例如,某些编译器对齐这样的对象是完全合理的:

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

So you can see how C++ classes might not map to how you expect them to be layed out in C#. 因此,您可以看到C ++类可能不会映射到您期望它们在C#中进行布局的方式。

There are flags to control this - you can set the packing in C++ to be 0, then use sequential layout in C#, and then your approach is reasonable. 有一些标志可以控制这一点-您可以在C ++中将打包设置为0,然后在C#中使用顺序布局,然后您的方法才是合理的。

Your use of properties is not a major problem - as long as you understand how and more importantly where the implicit backing field is going to be generated for you by the compiler. 使用属性不是主要问题-只要您了解编译器将如何为您生成隐式支持字段,更重要的是在何处生成隐式支持字段。

I finally found the real issue in what I was trying to do. 我终于在我试图做的事情中找到了真正的问题。 The callback I have from the SDK I'm using sends me the struct Base and by analyzing the fields inside I determine of which inherited type it is. 我正在使用的SDK中的回调向我发送了struct Base并通过分析其中的字段确定了它是哪种继承类型。 Then I have to "cast" the base type to the inherited type. 然后,我必须将基本类型“转换”为继承的类型。 Here is what I was initially doing: 这是我最初的工作:

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

This way of doing it will never go outside the size of the struct Base . 这种方式永远不会超出struct Base的大小。 Therefore, all the extra fields of the Inherited type won't be initialized correctly (reading outside of the copied memory). 因此,将不会正确初始化Inherited类型的所有其他字段(在复制的内存之外读取)。 I ended up doing it in an unsafe manner as follow: 我最终以不安全的方式这样做,如下所示:

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

This way, inherited points to the original memory block and may read outside of base size. 这样, inherited指向原始存储块,并且可能会在base大小之外读取。

Thanks for all your previous answers! 感谢您之前的所有回答! I actually built a C++ application to validate the sizes and offsets of all the C++ models I had. 我实际上构建了一个C ++应用程序来验证我拥有的所有C ++模型的大小和偏移量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM