19

I am trying to go through the mess of COM interop definitions we have scattered across various projects and collect them into a single, known-good location from which the whole dev team can benefit. Part of this effort involves cleaning up the definitions that have been accumulated over the years.

Some of these are borrowed from other source code, some were copied verbatim from pinvoke.net, and some look to be directly translated from the SDK headers. One thing I notice is that there is no consistency about when to use the various marshalling attributes, (even among the pinvoke.net examples this is pretty hit-or-miss). Part of the problem is, I don't think anyone here (myself included) fully understands when the various attributes are needed or not, or what they actually do. Up to this point, getting these right seems to be a combination of guesswork and random changes until the COMExceptions stop happening, but I'd much rather the translations be correct because someone actually looked at them and declared them so.

So, I'm starting with [In] and [Out]. I know what those two attributes do conceptually: they inform the marshaller which direction the data has to go. I assume the marshaller, for example, won't bother copying [In] data back to the caller, or knows that [Out] data might need to be freed on the callee side, etc. The things I don't know are:

  1. When is it necessary to use these attributes? That is, when is the default marshalling behavior wrong such that the attributes make it correct?
  2. When is it safe to use these attributes? That is, will specifying what should already be the default behavior change the way the marshaller works?
  3. When is it dangerous to use these attributes? I assume that explicitly marking an output parameter [In] is bad, but is marking an input parameter [In, Out] actually going to break anything?

So, given a hypothetical COM Interface method whose IDL looks like this:

HRESULT Foo(
    [in] ULONG a,
    [out] ULONG * b
    [in, out] ULONG * c);

I might see this translated as any of the following:

void Foo(
  uint cb,
  out uint b,
  ref uint c);

void Foo(
  uint cb,
  [Out] out uint b,
  [In, Out] ref uint c);

void Foo(
  [In] uint cb,
  [Out] out uint b,
  [In, Out] ref uint c);

Is there any functional difference between those three? Is any of them considered "better" than the others for reasons besides technical correctness?

Michael Edenfield
  • 28,070
  • 4
  • 86
  • 117
  • 2
    Be careful here, you are comparing apples and oranges. The [in] and [out] attributes in an IDL file are only used by midl.exe when it generates the proxy/stub code. Which are only used when a COM call needs to be marshaled between apartments or processes. The .NET [In] and [Out] attributes are only used by the pinvoke marshaller. Which runs in a very different runtime environment, always in-process and without any regard for apartments. Their intention is certainly the same, optimizing the call. – Hans Passant Dec 21 '11 at 18:23
  • 1
    Yes, I (think I) understand this part. For my purposes, when translating IDL to C#, I only use the IDL attributes as a hint to how the methods are "supposed to" behave. IOW, the [out] attribute in IDL means that the interface writer intends this parameter to be coming back from the caller with a value stored in it, so I need to make sure the C# code acts the same way. Where I'm falling short is understanding the default behavior of the .NET marshaller and when I need to tell it what to do vs. when it does the right thing on its own. – Michael Edenfield Dec 21 '11 at 18:30
  • 2
    They are necessary in IDL because it is completely unclear what the data flow direction is when you use pointers. Not an issue in C# with the *ref* and *out* keywords. The 99% good case is simply omitting the attributes in C#. 100% if you are actually replacing COM code with C#. – Hans Passant Dec 21 '11 at 18:39

1 Answers1

18

No, it's not strictly necessary to apply any of these attributes in your own code. They're all optional, and automatically applied to your code depending on the type of the parameter.

Essentially, they have the following C# keyword equivalents:

  • You get [In] by default.
  • The ref keyword gets you [In, Out].
  • The out keyword gets you [Out].

All documented in a nice table here.

You only need to use them when you want to alter those implied semantics or change the default behavior of the marshaler. For example, you can mark a parameter declared ref with the [In] attribute only to suppress the "out" part of marshaling.

That said, I find myself using them because they're a very easy way of making code self-documenting.

Ritch Melton
  • 11,498
  • 4
  • 41
  • 54
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • I like the self-documentary idea! And just adding: The use of MarshalAs for Bool and Strings is much more relevant, check these also... – eFloh Dec 21 '11 at 17:12
  • Yeah, I'm more familiar with the MarshalAs attribute, though there's a some details there I'm still unclear about (mostly with arrays). I was starting simple :) – Michael Edenfield Dec 21 '11 at 17:58
  • Ah, that actually explains something I see on pinvoke.net a lot! For example, if a method takes a REFIID parameter, normally that would be a "ref Guid". But if the parameter is designated as [in] in the IDL, I can designate it as "[In] ref Guid refiid" and it won't bother to marshal the Guid structure back to me since I don't need it, correct? – Michael Edenfield Dec 21 '11 at 18:00
  • @Michael: Yes, that's correct. You won't be able to see any modifications made to the structure, but if you don't need them, then marking it `[In] ref` is just fine. That said, this is definitely a micro-optimization and probably isn't worth too much hand-wringing over about getting it right. – Cody Gray - on strike Dec 22 '11 at 04:55
  • If I understand correctly, the `ref` and `out` keywords also add a level of indirection. For example if you have a class `X`, then saying `f(out x)` in managed code is like saying `f(**x)` in unmanaged code. So if the unmanaged signature is `f(*x)` where x is modified, you must use the `[Out]` attribute (or make x a struct). Source: http://msdn.microsoft.com/en-us/library/awbckfbz(v=vs.110).aspx – Ohad Schneider May 31 '14 at 12:32
  • @Ohad Yes, that's correct. But you're talking about the `ref` and `out` *keywords*. The question was about the *attributes*. Like I said in my answer, the keywords imply the attributes. But the attributes are—strictly speaking—optional. – Cody Gray - on strike May 31 '14 at 12:38
  • When using [ReadConsoleInput](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx) in C# 'out INPUT_RECORD[] lpBuffer' does not work, but '[Out] INPUT_RECORD[] lpBuffer' does, why is this? – SWdV Jul 17 '15 at 13:56
  • You need to fix this answer Cody, it is not correct. Those attributes affect what the pinvoke marshaller does, it knows nothing about the distinction between out and ref in the C# language. [Out] often has no effect at all because the object is already blittable so the native code directly writes to the GC heap. The attributes in IDL affect what the proxy and stub do, a completely unmanaged detail, avoiding copying arrays across the apartment boundary is better if that isn't necessary. – Hans Passant Sep 08 '16 at 14:59
  • @hans My inbox overflowed a while ago and I didn't look at all hundred or so comments, so I missed yours. Just earned a badge for this answer and saw your comment. It was my understanding that the corresponding C# keywords caused these attributes to be emitted in the IDL, thus affecting marshaling. I haven't actually verified this, but it does seem like that's what the documentation suggested. Has something changed here, or was my understanding always incorrect? I'm afraid I may be out of my depth here in updating this. It's been a while since I wrote managed code and I've forgotten a lot. – Cody Gray - on strike Dec 13 '16 at 07:04
  • This does not seem to be correct. I recently encountered a situation where passing a class to a native method using `ref` did not allow the class to be updated by the native method; however, if I used `[In, Out]` in the method declaration, it worked. – Herohtar Dec 08 '19 at 04:39
  • Way down toward the bottom of the page on marshaling classes, in the [SysTime sample](https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-classes-structures-and-unions#systime-sample): *"The parameter must be declared with the `InAttribute` and `OutAttribute` attributes because classes, which are reference types, are passed as `In` parameters by default. For the caller to receive the results, **these directional attributes must be applied explicitly**."* – Herohtar Dec 08 '19 at 05:12