0

I was testing around with P/Invoke stuff today and ran into something that greatly confuses me.

I have a unmanaged library with functions taking array parameters, printing out their values and modifying them:

#include <cstdio>

#define export extern "C" void __cdecl __declspec(dllexport)

struct S
{
    int x;
};

export PassIntArray(int* x, int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("x[%i] = %i\n", i, x[i]);
        x[i] *= 10;
    }
}

export PassSArray(S* s, int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("s[%i].x = %i\n", i, s[i].x);
        s[i].x *= 10;
    }
}

As well as a C# program accessing those function via P/Invoke:

using System.Runtime.InteropServices;
using static System.Console;

namespace Test
{
    [StructLayout(LayoutKind.Sequential)]
    struct S
    {
        public int X;
    }

    class Program
    {
        [DllImport("mylib.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void PassIntArray(int[] x, int size);

        [DllImport("mylib.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void PassSArray(S[] s, int size);

        static void Main(string[] args)
        {
            var z = new[] {1,2,3};
            PassIntArray(z, z.Length);
            foreach (var i in z)
                WriteLine(i);

            var u = new[] { new S { X = 1 }, new S { X = 2 }, new S { X = 3 } };
            PassSArray(u, u.Length);
            foreach (var i in u)
                WriteLine(i.X);
        }
    }
}

Running this program, the output for the array functions is as follows:

// Unmanaged side:
x[0] = 1
x[1] = 2
x[2] = 3
// Managed side, after modification:
10
20
30

But, for PassSArray, it's this:

// Unmanaged side:
s[0].x = 1
s[1].x = 2
s[2].x = 3
// Managed side, after modification:
1
2
3

From this question: "Happens when the value is blittable, an expensive word that means that the managed value or object layout is identical to the native layout. The pinvoke marshaller can then take a shortcut, pinning the object and passing a pointer to managed object storage. You'll inevitably see changes then since the native code is directly modifying the managed object."

From my understanding, S should be blittable (since it uses sequential layout and only contains fields of types which are blittable), and so should get pinned by the marshaller, causing modifications to 'carry over' as they do with PassIntArray. Where's the difference, and why?

LunarLambda
  • 389
  • 2
  • 5
  • Did you try it without `[In, Out]`? – MetaColon May 12 '18 at 21:42
  • Oh that was from testing, didn't realise I kept that in when posting [In] seems to be the default behaviour, [Out] makes everything 0 / garbage values and [In, Out] works as I want it to, the same way the int[] one does. My question is why the behaviour *without* any attributes is different. – LunarLambda May 12 '18 at 21:44
  • Would have been weird if it had made a difference anyway. Can't quite explain this behaviour. – MetaColon May 12 '18 at 21:46
  • Wild guess: `int[]` is pinned, while `S[]` is copied. – user4003407 May 12 '18 at 21:58
  • @PetSerAl Why though? [In, Out] attributes should at least prevent that, shouldn't they? – MetaColon May 12 '18 at 22:01
  • The reason is stated nicely [here](https://stackoverflow.com/a/33817457/4228458). – CodingYoshi May 12 '18 at 22:09
  • Possible duplicate of [What is the difference between \[In, Out\] and ref when using pinvoke in C#?](https://stackoverflow.com/questions/33815276/what-is-the-difference-between-in-out-and-ref-when-using-pinvoke-in-c) – CodingYoshi May 12 '18 at 22:09
  • But according to that, S should be blittable, and so have the same behaviour as int[], getting pinned by the marshaller instead of copied. – LunarLambda May 12 '18 at 22:27
  • What `[In, Out]` should prevent? Copying or pining? As far as I understand, `[In]` or `[Out]` have nothing to do with it. Technically marshaler can always make a copy and that would be correct behavior. Pinning is just a performance optimization, that it takes sometimes. – user4003407 May 12 '18 at 22:39
  • I agree that it should be blittable and so pinned. Don't understand. However, you have a calling convention mismatch. – David Heffernan May 13 '18 at 07:13
  • Where? Guess I should specify Cdecl on the dllimports, don't think it makes a difference though.. – LunarLambda May 13 '18 at 09:55

0 Answers0