0
votes

I have a C++ .dll (unable to make changes to the code) from which I am trying to call a function that takes a reference parameter of a Struct type (also defined in the .dll).

The function requires the Struct to have the 'paramName' and 'groupName' fields populated, and based on those fields, it will return the Struct with the remaining fields populated.

I am not getting any marshaling errors, however, the library call returns an error code and a debug log shows that it is receiving an empty string for the two string fields (see below for how fields are set). My assumption is that the size of this struct is not aligning between the managed and unmanaged representation, and thus the struct is not blittable.

Here is the C++ method signature:

int GetConfigs(int contextHandle, Configs* configs);

And the C++ Configs struct:

struct Configs {    
    int myInt;
    float myFloat;
    bool flag;
    char name[64];
    char group[64];
}  

The C# function wrapper:

[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetConfigs(int contextHandle, [MarshalAs(UnmanagedType.Struct), In, Out] ref Configs configs);

The C# struct definition:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Configs
{
    public int myInt;
    public float myFloat;
    [MarshalAs(UnmanagedType.U1)]
    public bool flag;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string group;
}

As per the Microsoft documentation I have declared the C++ char[]'s to be represented in C# by strings and marshaled as ByValTStr with a set size. Also from Microsoft documentation:

  • bool is not blittable, so I mark it with an explicit MarshalAs.
  • float is also not blittable, but declaring these fields as a float or a double made no difference in the original issue.

Lastly, the C# calling code:

var configs = new Library.Configs
{
    name = "testName",
    group = "testGroup"
};

var returnCode = Library.GetConfigs(GetContextId(), ref configs);

The return code comes back as a failure code and the library debug output file shows that the struct argument had:

name = []

(name is clearly set to what I expect it to be at the time of the call when I debug through the C# code)

Instead of declaring the string fields as

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;

I tried:

[MarshalAs(UnmanagedType.ByValArray,  SizeConst = 64)]
public byte[] name;

but that also made no difference.

1
The bool field is marshalled wrong, it is a 1-byte type in C++. Use MarshalAs(UnmanagedType.U1). Next time you can verify this by ensuring that Marshal.SizeOf() on the struct returns the same value as sizeof does in C++. - Hans Passant
Thanks. I have updated my code and my original question with the correct marshaling attribute. That did not fix the problem though, so I will keep my question open - Kevin
try this... public class ParameterConfigs { public int scale; public int interpolationOrder; public float blurSigma; public float multiplier; public bool positiveOnly; public string paramName = new string(new char[64]); public string group = new string(new char[64]); } - vivek nuna

1 Answers

1
votes

The float/double was what was throwing off the struct byte size- the library was expecting a 4 byte floating point number but the data marshaler by default keeps those as 8 bytes. Fixing that along with the boolean as mentioned in the comments above solved the problem:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Configs
{
    public int myInt;
    [MarshalAs(UnmanagedType.R4)]
    public float myFloat;
    [MarshalAs(UnmanagedType.U1)]
    public bool flag;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string group;
}