4

How to pass NULL to a parameter of a COM interface method if it is defined like [In, Out] ref int pchEaten?

For example, consider the following interface:

[ComImport, Guid ("000214E6-0000-0000-C000-000000000046")]
[InterfaceType (ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellFolder
{
    void ParseDisplayName (
        [In] IntPtr hwnd,
        [In] IBindCtx pbc,
        [In, MarshalAs (UnmanagedType.LPWStr)] string pszDisplayName,
        [In, Out] ref uint pchEaten,
        [Out] out PIDLIST ppidl,
        [In, Out] ref SFGAO pdwAttributes);
    // ...
}

MSDN says the following about the pchEaten parameter: A pointer to a ULONG value that receives the number of characters of the display name that was parsed. If your application does not need this information, set pchEaten to NULL, and no value will be returned. The pdwAttributes parameter can also be set to NULL.    

However, when I call the ParseDisplayName method from C#, I see no way to pass null to a ref parameter.

If I were calling functions from a DLL, I could import a function multiple times: one with IntPtr parameter, one with proper signature, and choose the overload depending on wether I need to pass and receive the values. However, if I try importing the same method multiple times in COM, it won't work as the order of methods is crucial and function pointers will shift.

Question: How to make calling a COM method both with a value and NULL of an in/out parameter possible?

Note: This is just an example. I know I can create a dummy variable, pass it to a ref parameter and ignore the returned value. However, behavior of a method may depend on wether the value is NULL, non-null value may incur performance costs etc., so I'd like to avoid it.

Athari
  • 33,702
  • 16
  • 105
  • 146
  • Have a look at this answers: http://stackoverflow.com/questions/736474/c-how-to-pass-null-to-a-function-expecting-a-ref – Florian Gl Sep 24 '13 at 07:35
  • @FlorianGl This question and answers for it do not cover my case, a COM interface. As I've already mentioned, I know the solution for the DLL case. – Athari Sep 24 '13 at 07:39
  • This doesn't solve your problem, but this parameter must be `out`, not `ref`. – sharptooth Sep 24 '13 at 07:46
  • @sharptooth It's `[in, out, unique, annotation("__reserved")] ULONG *pchEaten` in IDL, but `[out] ULONG *pchEaten` in the documentation. Weird. Logically, it should be `out`, yes. – Athari Sep 24 '13 at 07:55
  • Ouch. IDL mismatching the documentation is kinda worrying. – sharptooth Sep 24 '13 at 08:01
  • Florian's comment and associated link is valid. You must re-declare the COM interface with pchEaten as IntPtr, if you really want to pass null. – Simon Mourier Sep 24 '13 at 08:07
  • @SimonMourier If I declare the parameter as `IntPtr`, I won't be able to pass and receive values. I need *both* cases. – Athari Sep 24 '13 at 08:09
  • Of course you can pass a struct if you declare as IntPtr, but you'll need to use Marshal.StructureToPtr. – Simon Mourier Sep 24 '13 at 08:11
  • @SimonMourier Could you provide a complete sample (how to pass and receive `int` value through `IntPtr` parameter)? I can't find a relevant example on google. – Athari Sep 24 '13 at 08:20
  • Possible duplicate of [C#: How to pass null to a function expecting a ref?](https://stackoverflow.com/questions/736474/c-how-to-pass-null-to-a-function-expecting-a-ref) – Jim Fell Jun 07 '19 at 18:06
  • @JimFell COM interop is different from DLL interop. Please read the question. – Athari Jun 07 '19 at 22:55

2 Answers2

4

You can redefine the interface like this for example:

internal interface IShellFolder
{
    void ParseDisplayName (
        [In] IntPtr hwnd,
        [In] IBindCtx pbc,
        [In, MarshalAs (UnmanagedType.LPWStr)] string pszDisplayName,
        IntPtr pchEaten,
        [Out] out PIDLIST ppidl,
        [In, Out] ref SFGAO pdwAttributes);
    // ...
}

And call it like this if you want to pass null:

ParseDisplayName(...., IntPtr.Zero, ...);

Or like this if you want to pass a uint value:

IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(uint)));
uint i = 1234;
Marshal.StructureToPtr(i, p, false);
ParseDisplayName(...., p, ...);
i = (uint)Marshal.PtrToStructure(p, typeof(uint)); // read the value back
Marshal.FreeHGlobal(p);
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • +1, would [`Marshal.ReadInt32`](http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.readint32.aspx) work for reading the return value? – vgru Sep 24 '13 at 08:51
  • Is allocating memory necessary? If I understood MSDN ([Blittable and Non-Blittable Types](http://msdn.microsoft.com/en-us/library/75dwhxf7.aspx)) correctly, blittable types are pinned instead of copied, and `int` is blittable. Ideally, I'd like to replicate this behavior. – Athari Sep 24 '13 at 09:02
  • @Groo - good remark, I have updated my post. You could use Marshal.ReadInt32 (and cast to uint - unchecked), but I have put a code that will work if you replace uint by any struct. – Simon Mourier Sep 24 '13 at 10:12
  • @Athari - int is blittable, but you want to pass a (possibly null) pointer/reference to an int (or uint here), not an int. – Simon Mourier Sep 24 '13 at 10:14
3

It strikes me that you are probably trying too hard to avoid pointers. The C# language supports them just fine, you can declare the argument as [In] uint* pchEaten. Now you have all of the options available that you have in a native language, you can pass &local as well as null. Where local should be a local variable of the calling method so that the pointer is stable and you won't have to fret about pinning.

But yes, you do have to declare the method unsafe. Of course it is not when you program it correctly :)

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536