2

I'm playing with C++ interoperability and specifically with C# at the moment. I have a C++ DLL containing a C style API that I want to make available for C# and most of the time I am finding that it is not too bad. However, I am having a terrible time with bool arrays. The below code and output should explain the problem.

The bool array can be passed in, examined and altered, but any changes do not remain in the C# code after returning from the dll. By contrast if you try this with say a double array it works perfectly. You don't even need to use a MarshalAs attribute in the declaration and can pass it as tried below with TestMethod19. I believe the reason is that a bool size on C# is 4 bytes but is 1 byte on C++ (checked with Marshal.SizeOf and sizeof respectively), but what can I do to make this work?

Passing Array of Bool to C++ Code from C# does not appear to be a duplicate. The responses say to Marshal as an I1, which my tests show doesn't work. I think that question was meant for a bool array in variable that is only meant for reading. However I am also looking for this array to be an out variable.

C++ Declaration .h:

extern "C" DECLSPEC void TestMethod15(bool* d);

C++ Definition .cpp:

void TestMethod15(bool* d)
{
    cout << "Inside Test Method 15.  ";
    cout << "Printing bool array param: ";

    for (int i = 0; i < 5; i++)
    {
        cout << d[i] << ",";
    }

    cout << endl << "Putting values in the bool array: ";

    for (int i = 0; i < 5; i++)
    {
        d[i] = i % 2 == 0;
        cout << d[i] << ",";
    }
    cout << endl;
}

C# Declarations

public class QuizzerEngine
{
    [DllImport("QuizzerEngine.DLL", CallingConvention = CallingConvention.Cdecl)]
    public static extern void TestMethod15(
        [param: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1)] bool[] bAr);

    [DllImport("QuizzerEngine.DLL", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestMethod15")]
    public static extern void TestMethod16(
        [param: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)] bool[] bAr);

    [DllImport("QuizzerEngine.DLL", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestMethod15")]
    public static extern void TestMethod17(
        [param: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Bool)] bool[] bAr);

    [DllImport("QuizzerEngine.DLL", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestMethod15")]
    public static extern void TestMethod18(
        [param: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.VariantBool)] bool[] bAr);

    [DllImport("QuizzerEngine.DLL", CallingConvention = CallingConvention.Cdecl, EntryPoint = "TestMethod15")]
    public static extern void TestMethod19(
        bool[] bAr);
}

C# Calls

{
    bool[] boolArray = new bool[5];
    boolArray[1] = true;
    QuizzerEngine.TestMethod15(boolArray);
    Console.WriteLine("After TM15: {0},{1},{2},{3},{4}\n", boolArray[0], boolArray[1], boolArray[2], boolArray[3], boolArray[4]);
}
{
    bool[] boolArray = new bool[5];
    boolArray[1] = true;
    QuizzerEngine.TestMethod16(boolArray);
    Console.WriteLine("After TM16: {0},{1},{2},{3},{4}\n", boolArray[0], boolArray[1], boolArray[2], boolArray[3], boolArray[4]);
}
{
    bool[] boolArray = new bool[5];
    boolArray[1] = true;
    QuizzerEngine.TestMethod17(boolArray);
    Console.WriteLine("After TM17: {0},{1},{2},{3},{4}\n", boolArray[0], boolArray[1], boolArray[2], boolArray[3], boolArray[4]);
}
{
    bool[] boolArray = new bool[5];
    boolArray[1] = true;
    QuizzerEngine.TestMethod18(boolArray);
    Console.WriteLine("After TM18: {0},{1},{2},{3},{4}\n", boolArray[0], boolArray[1], boolArray[2], boolArray[3], boolArray[4]);
}
{
    bool[] boolArray = new bool[5];
    boolArray[1] = true;
    QuizzerEngine.TestMethod18(boolArray);
    Console.WriteLine("After TM19: {0},{1},{2},{3},{4}\n", boolArray[0], boolArray[1], boolArray[2], boolArray[3], boolArray[4]);
}

Output:

Inside Test Method 15.  Printing bool array param: 0,1,0,0,0,
Putting values in the bool array: 1,0,1,0,1,
After TM15: False,True,False,False,False

Inside Test Method 15.  Printing bool array param: 0,1,0,0,0,
Putting values in the bool array: 1,0,1,0,1,
After TM16: False,True,False,False,False

Inside Test Method 15.  Printing bool array param: 0,0,0,0,1,
Putting values in the bool array: 1,0,1,0,1,
After TM17: False,True,False,False,False

Inside Test Method 15.  Printing bool array param: 0,0,255,255,0,
Putting values in the bool array: 1,0,1,0,1,
After TM18: False,True,False,False,False

Inside Test Method 15.  Printing bool array param: 0,0,255,255,0,
Putting values in the bool array: 1,0,1,0,1,
After TM19: False,True,False,False,False
jww
  • 97,681
  • 90
  • 411
  • 885
DisplayName
  • 239
  • 3
  • 7
  • 1
    If the marshal notation in C# can't specify an item size for the array, then use a C# type of the right size, namely 1 byte. I assume that must exist. – Cheers and hth. - Alf Sep 02 '18 at 03:49
  • See [PInvoke and bool (or should I say BOOL)?](https://blogs.msdn.microsoft.com/jaredpar/2008/10/14/pinvoke-and-bool-or-should-i-say-bool/) on MSDN – Remy Lebeau Sep 02 '18 at 06:00
  • 2
    Possible duplicate of [Passing Array of Bool to C++ Code from C#](https://stackoverflow.com/questions/31410324/) – Remy Lebeau Sep 02 '18 at 06:02
  • 1
    Thank you for the responses. @RemyLebeau the top response on that duplicate answer is wrong. You can see I tried it above with testmethod15 and it appears to work but the bool array changes don't stay at the C# level. He also claims C# bools are 1 byte and C++ bools are 4, which is the opposite of what my test showed. Looking further it seems that Marshal.SizeOf is known to be wrong for bools. https://stackoverflow.com/questions/18039000/sizeof-operator-for-types I guess marshalling for I4 should work but it doesn't. it's almost like marshaling only works one direction and I need both. – DisplayName Sep 02 '18 at 14:57
  • @Cheersandhth.-Alf This works! You can use a byte array on the C# side, no marshaling and call the unchanged C++ method with bool* as the parameter type. It is not ideal though, and I'd love to know a way to do this without having to do a byte bool conversion afterwards. Until then I will use this. – DisplayName Sep 02 '18 at 15:07
  • You also need to be aware that on some system it is assume that boolean type is stored as 0/1 while on other it might be 0/-1 or even 0/anything else. So it might lead to obscur bugs. Usually when using P/Invoke, one would use Win32 API types (that is BOOL) and do appropriate conversion in C++ code. – Phil1970 Sep 02 '18 at 20:30
  • I have always seen clients conforming to dlls, not the other way around. – DisplayName Sep 03 '18 at 17:17

0 Answers0