out parameters might be NULL in managed code - using causes Exception, ignoring causes Compiler error

What's one to do?

Recently MicroFocus found this issue with one of the interop definitions in VS 2005.  The definition for IVsContainedLanguageCodeSupport::GetCompatibleEventHandlers is as follows:

Microsoft.VisualStudio.TextManager.Interop.IVsContainedLanguageCodeSupport.GetCompatibleEventHandlers(string, string, string, out int, out System.IntPtr, out System.IntPtr)

The problem is with the last "out System.IntPtr" parameter.  It is labeled as "ppbstrMemberIDs" and is designed to be a pointer to an arrary of BSTR's that contains the Member IDs of the event handlers for a piece of code.  Now, you have to be implementing a language service for a Web project in VS2005 in order to run into this and there aren't that many of you out there, but the principle is important for all interop developers so please read on.

In the Beta2 implementation of VS that method will get called with a NULL ppbstrMemberIDs parameter in native code.  This is perfectly legal in native code.  You simply ignore the parameter because it is NULL.  In managed code, however, that won't work because it is marked as an 'out' parameter.  You have to do something with the value in order to satisfy the compiler otherwise the compiler gives you an error.  Further more, the problem is compounded by the fact that because the value is actually NULL, the runtime sees you referencing a NULL variable and it will raise a null reference exception when you something with it.  Catch 22: you can't ignore it because the compiler complains and you can't assign it because the runtime complains.

Microfocus reported the problem to us and we rectified it in the native implementation.  It no longer passes NULL.  But there's more to the story...

What if you need to get B2 working?  What if something ships like this because it wasn't caught in testing?  Well, here's the beauty of the thing MicroFocus came up with as a workaround.  (By the way, I have their permission to blog this and mention them.) You can employ this for other methods and parameters that are optional in native code, but aren't marked as [opt] in the IDL definition that drives the interop specification.  I haven't tried it yet, but I suspect it will work.  If I find time maybe I'll try IVsQueryEditQuerySave2.QueryEditFiles since I've heard a report on this one, but haven't looked into it directly.

First you need to declare a PInvoke stub for lstrcpyn (an OS API):

/// <summary>

/// Hack to enable one to "assign" a value to an out IntPtr when the IntPtr has

/// not been assigned by the underlying native code.

/// </summary>

/// <param name="destPtr"></param>

/// <param name="srcPtr"></param>

/// <param name="maxLength"></param>

/// <returns></returns>

[DllImport("kernel32")]

extern public static IntPtr lstrcpyn(out IntPtr destPtr, IntPtr srcPtr, int maxLength);

Then, inside your implmentation of IVsContainedLanguageCodeSupport::GetCompatibleEventHandlers, you call

            lstrcpyn(out ppbstrMemberIDs, new IntPtr(0), 0);

where ppbstrMemberIDs is the out parameter the compiler insists get assigned but causes the null reference exception at runtime.

The C# compiler is satisfied that it's been assigned some data, but since the the length passed in to the copy routine is 0, nothing is actually touched at runtime.

I suspect there are other API's one can use for this purpose and I bet there's also a nice way to do the same thing in C# directly with 'unsafe' code, but I haven't had time to look into that yet.  I just wanted to get this out there and provide a workaround. 

Please, if you find issues that need this workaround, file a bug at https://lab.msdn.microsoft.com/productfeedback/ so we can get the issue addressed with the need of a hack like this.

Allen