Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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).

like image 463
Joel Bourbonnais Avatar asked Oct 19 '22 02:10

Joel Bourbonnais


1 Answers

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.

like image 190
Hans Passant Avatar answered Nov 12 '22 21:11

Hans Passant