PInvoke Error in .NET 4: Array size control parameter index is out of range

So in a code-base I was working in yesterday, we use PInvoke to call out to the Performance
Data Helper (PDH) API’s to collect performance information for machines without using
Perfmon.  One of those PInvoke calls looked like this:

 /*

PDH_STATUS PdhExpandCounterPath(

LPCTSTR szWildCardPath,

LPTSTR mszExpandedPathList,

LPDWORD pcchPathListLength

);

*/

[DllImport("pdh.dll", CharSet = CharSet.Unicode)]

private static extern PdhStatus PdhExpandCounterPath(

string szWildCardPath,

[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] char[] mszExpandedPathList,

ref uint pcchPathListLength

);

In .NET 3.5 and below, this PInvoke call works perfectly fine.  In .NET 4.0,
though, I saw this exception:

 System.Runtime.InteropServices.MarshalDirectiveException: 


Cannot marshal 'parameter #2': Array size control parameter index is out of range. 


at System.Runtime.InteropServices.Marshal.InternalPrelink(IRuntimeMethodInfo m) 


at System.Runtime.InteropServices.Marshal.Prelink(MethodInfo m)

So, can you identify what’s wrong in the code above?

Well, the Array size control parameter index indicates the zero-based parameter that
contains the count of the array elements, similar to size_is in COM.  Because
the marshaler cannot determine the size of an unmanaged array, you have to pass it
in as a separate parameter.  So in the call above, parameter #2, we specify “SizeParamIndex
= 3” to reference the pcchPathListLength parameter to set the length of the array. 
So what’s the catch?

Well, since the SizeParamIndex is a zero-based index, the 3rd parameter doesn’t really
exist.  So, to fix this, we just change the “SizeParamIndex=3” to “SizeParamIndex=2”
to reference the pcchPathListLength:

 /*

PDH_STATUS PdhExpandCounterPath(

LPCTSTR szWildCardPath,

LPTSTR mszExpandedPathList,

LPDWORD pcchPathListLength

);

*/

[DllImport("pdh.dll", CharSet = CharSet.Unicode)]

private static extern PdhStatus PdhExpandCounterPath(

string szWildCardPath,

[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] char[] mszExpandedPathList,

ref uint pcchPathListLength

);

It looks like in .NET 3.5 and below, though, we allowed you to reference either 1-based
index or a zero-based index but in .NET 4.0, we buttoned that up a bit and force you
to use the zero-based index.    Big thanks to my co-worker and frequent
collaborator, Zach Kramer for
his assistance in looking at this issue.

Until Next Time!