Search code examples
c#arraysinteropdllimportnative-code

Can I modify (fill) array that I pass from C# to C++?


I have the next C# method:

internal static class FillArrayApi
{
    [DllImport("NativeLibrary.dll", EntryPoint = "fill_array")]
    internal static extern void FillArray(byte[] array, int length);
}

It is implemented in the native code like this:

extern "C" __declspec(dllexport) void fill_array(uint8_t* array, int length);

void fill_array(uint8_t* array, const int length)
{
    for (int i = 0; i < length; i++)
    {
        array[i] = 1;
    }
}

I use the native code to fill a managed array like this:

internal static class Program
{
    public static void Main()
    {
        var array = new byte[] { 0, 0, 0, 0, 0 };
        Console.WriteLine("Array before filling: " + string.Join(", ", array));
        FillArrayApi.FillArray(array, array.Length);
        Console.WriteLine("Array after filling: " + string.Join(", ", array));
    }
}

The full implementation is here.

FillArray indeed fills my array with 1s. Is this a proper way to fill the array in the native code or I am just lucky that it works?

From the docs I can see that sometimes MarshalAs attribute is needed, also I have to use Out attribute in some cases. Do I have to use them in my case as well or it is fine to rely on the default behavior?

Edit: posted the whole code and unhardcoded the array length as suggested in the comments.

Edit #2: the return type of FillArray should be void of course, because the native function is void.


Solution

  • Your DllImport definition is wrong.

    It should have

    • CDecl calling convention
    • void return type
    • [Out] on the array, as well as MarshalAs with a SizeParamIndex.
    [DllImport("NativeLibrary.dll", EntryPoint = "fill_array", CallingConvention = CallingConvention.CDecl)]
    internal static extern void FillArray(
      [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1] byte[] array,
      int length);
    

    While [Out] is not necessary in some circumstances (when the array is pinned), it is advisable to add it anyway. The docs say:

    With pinning optimization, a blittable array can appear to operate as an In/Out parameter when interacting with objects in the same apartment. However, if you later export the code to a type library used to generate the cross-machine proxy, and that library is used to marshal your calls across apartments, the calls can revert to true In parameter behavior.

    [MarshalAs] is necessary here for the same reason. While the default type will be LPArray anyway, SizeParamindex is needed for marshalling the array back.

    Again, if pinning is used then it wouldn't be necessary, but that doesn't always happen. So if you are sure you will never do a cross-process (or cross-COM-apartment) call, only then you can leave off [Out] and [MarshalAs].