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!