简体   繁体   中英

C# marshaling C++ struct inheritance

Let's say I have the following structs in 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#. 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 ?

I'm using the IBase as a way to enforce the base fields to be present.

Unfortunately, I don't have control over the C++ side (SDK for an external system I integrate with).

The word "appropriate" does not exactly apply to these C# declarations. 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#. Start by writing a little test program to check that, the C++ version for this snippet should look like this:

#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. Do the exact same thing in 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. 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. 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. 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#.

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.

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. 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 . Therefore, all the extra fields of the Inherited type won't be initialized correctly (reading outside of the copied memory). 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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