What is the difference between [In, Out] and ref when using pinvoke in C#?
Asked Answered
H

1

24

Is there a difference between using [In, Out] and just using ref when passing parameters from C# to C++?

I've found a couple different SO posts, and some stuff from MSDN as well that comes close to my question but doesn't quite answer it. My guess is that I can safely use ref just like I would use [In, Out], and that the marshaller won't act any differently. My concern is that it will act differently, and that C++ won't be happy with my C# struct being passed. I've seen both things done in the code base I'm working in...

Here are the posts I've found and have been reading through:

Are P/Invoke [In, Out] attributes optional for marshaling arrays? Makes me think I should use [In, Out].

These three posts make me think that I should use [In, Out], but that I can use ref instead and it will have the same machine code. That makes me think I'm wrong -- hence asking here.

Henson answered 19/11, 2015 at 22:9 Comment(4)
Could you give a concrete example please. Do you want to know the difference between [In, Out] ref int foo and ref int foo? As it stands, your question is lacking detail, and I believe that you need one (or more) concrete examples.Schroth
Something like, is: [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)] private static extern bool myFunction([In, Out] SomeStruct[] aStruct; the same as using ref in place of the [In, Out].Henson
Also, did you just combine [In, Out] and ref ;)?Henson
No, they are not the same.Schroth
C
34

The usage of ref or out is not arbitrary. If the native code requires pass-by-reference (a pointer) then you must use those keywords if the parameter type is a value type. So that the jitter knows to generate a pointer to the value. And you must omit them if the parameter type is a reference type (class), objects are already pointers under the hood.

The [In] and [Out] attributes are then necessary to resolve the ambiguity about pointers, they don't specify the data flow. [In] is always implied by the pinvoke marshaller so doesn't have to be stated explicitly. But you must use [Out] if you expect to see any changes made by the native code to a struct or class member back in your code. The pinvoke marshaller avoids copying back automatically to avoid the expense.

A further quirk then is that [Out] is not often necessary. 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.

Something you in general strongly want to pursue, it is very efficient. You help by giving the type the [StructLayout(LayoutKind.Sequential)] attribute, it suppresses an optimization that the CLR uses to rearrange the fields to get the smallest object. And by using only fields of simple value types or fixed size buffers, albeit that you don't often have that choice. Never use a bool, use byte instead. There is no easy way to find out if a type is blittable, other than it not working correctly or by using the debugger and compare pointer values.

Just be explicit and always use [Out] when you expect fields to get updated. It doesn't cost anything if it turned out not to be necessary. And it is self-documenting. And you can feel good that it still will work if the architecture of the native code changes.

Clotilde answered 20/11, 2015 at 1:25 Comment(7)
I'm fairly sure my objects aren't blittable (although I'm not positive). If I have something that is currently [In, Out], would changing it to just use [Out] be cleaner code and behave the same way? Or would it be cleaner code to leave the [In, Out] attribute to show two-way intent? Thanks for your answer.Henson
If two-way is the intent then describe it that way, do use [In, Out].Clotilde
If the value is blittable and not a reference type (in this case, a struct in C#), would using ref instead of using [In, Out] work given that the struct is sequential and contains only primitive types (int, float, double)? Would this be the same as using [In, Out] to the marshaller because in either case it would pin the object in memory?Henson
You didn't understand the answer, using ref is not optional and cannot be substituted with [In, Out]. You must generate a pointer if the native code requires one and that requires using ref. The next consideration is then the attributes to describe the data flow.Clotilde
Can you provide more references for your information?Nightmare
You can find this information on learn.microsoft.comNightmare
Are ref and out identical in this case? Are any potential initial contents of a out Foo foo visible to the native code or would it be default initialized?Forjudge

© 2022 - 2024 — McMap. All rights reserved.