Is there a way to pass a C++ struct containing another C++ struct back to C#?

Viewed 68

Suppose I have the following 2 C++ structs:

struct UsbErrorCodes
{
    const signed int UsbInterfaceErrorCode = 0;
    const DWORD WinDriverErrorCode = 0;

    UsbErrorCodes(const signed int usbInterfaceErrorCode, const DWORD winDriverErrorCode)
        : UsbInterfaceErrorCode(usbInterfaceErrorCode),
        WinDriverErrorCode(winDriverErrorCode)
    {
    }
};

struct NativeReadBytesResponse
{
    const unsigned long NumberOfBytesRead = 0;
    const UsbErrorCodes UsbErrorCodes;

    NativeReadBytesResponse(const unsigned long numberOfBytesRead, const ::UsbErrorCodes usbErrorCodes)
        : NumberOfBytesRead(numberOfBytesRead),
        UsbErrorCodes(usbErrorCodes)
    {
    }
};

And I have a C++ function returning a NativeReadBytesResponse:

NativeReadBytesResponse ReadBytes
(
    unsigned long numberOfBytesToRead,
    unsigned char* bytesBuffer,
    unsigned long transferTimeout = 3000
);

How do I setup a C# method to call that function and get me back that NativeReadBytesResponse?

I have tried this but get a Managed Debugging Assistant 'PInvokeStackImbalance' exception when it's called.

    [DllImport("MyNativeLibrary.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern NativeReadBytesResponse ReadBytes
    (
        uint numberOfBytesToRead,
        [Out]
        out byte[] bytesBuffer,
        uint transferTimeout = 3000
    );

I should also note that I have been able to return UsbErrorCodes from C++ to its C# equivalent struct by mimicing the struct in C# and adding [StructLayout(LayoutKind.Sequential, Size = 64)] on top of the struct. Here are the 2 C# structs which the 2 C++ structs should map to:

[StructLayout(LayoutKind.Sequential, Size = 64)]
public partial struct UsbErrorCodes : IEquatable<UsbErrorCodes>
{
    public static bool operator ==(UsbErrorCodes left, UsbErrorCodes right)
        => left.Equals(right);

    public static bool operator !=(UsbErrorCodes left, UsbErrorCodes right)
        => !left.Equals(right);

    [DisplayName("USB Interface Error Code")]
    public uint UsbInterfaceErrorCode { get; }

    [DisplayName("Windows Driver Error Code")]
    public WinDriverError WinDriverErrorCode { get; }
    
    [Newtonsoft.Json.JsonConstructor]
    public UsbErrorCodes
    (
        uint usbInterfaceErrorCode,
        WinDriverError winDriverErrorCode
    )
    {
        UsbInterfaceErrorCode = usbInterfaceErrorCode;
        WinDriverErrorCode = winDriverErrorCode;
    }

    public bool Equals(UsbErrorCodes other)
        => UsbInterfaceErrorCode.Equals(other.UsbInterfaceErrorCode)
        && WinDriverErrorCode.Equals(other.WinDriverErrorCode);

    public override bool Equals(object obj)
        => obj is UsbErrorCodes usbErrorCodes
        ? Equals(usbErrorCodes)
        : base.Equals(obj);

    public override int GetHashCode()
        => UsbInterfaceErrorCode.GetHashCode()
        ^ WinDriverErrorCode.GetHashCode();
}

[StructLayout(LayoutKind.Sequential, Size = 96)]
public struct NativeReadBytesResponse : IEquatable<NativeReadBytesResponse>
{
    public static bool operator ==(NativeReadBytesResponse left, NativeReadBytesResponse right)
        => left.Equals(right);

    public static bool operator !=(NativeReadBytesResponse left, NativeReadBytesResponse right)
        => !left.Equals(right);

    public uint NumberOfBytesRead { get; }

    [DisplayName("USB Error Codes")]
    public UsbErrorCodes UsbErrorCodes { get; }
    
    [Newtonsoft.Json.JsonConstructor]
    public NativeReadBytesResponse
    (
        uint numberOfBytesRead,
        UsbErrorCodes usbErrorCodes
    )
    {
        NumberOfBytesRead = numberOfBytesRead;
        UsbErrorCodes = usbErrorCodes;
    }

    public bool Equals(NativeReadBytesResponse other)
        => NumberOfBytesRead.Equals(other.NumberOfBytesRead)
        && UsbErrorCodes.Equals(other.UsbErrorCodes);

    public override bool Equals(object obj)
        => obj is NativeReadBytesResponse nativeReadBytesResponse
        ? Equals(nativeReadBytesResponse)
        : base.Equals(obj);

    public override int GetHashCode()
        => NumberOfBytesRead.GetHashCode()
        ^ UsbErrorCodes.GetHashCode();
}

Does C#-to-C++-to-C# interop not support objects nested within objects or is something else at fault here?

0 Answers
Related