Marshalling Output Arrays
I recently ran into an issue getting values out of a function that you pass an int* to get an array out. The C function looks like this:
int __stdcall UP_GetProgList(int prog_type, int *sn_list, int count, int *count_returned)
Naively, I wrote the following P/Invoke call:
[DllImport(@"the.dll")] private static extern int UP_GetProgList(int prog_type, out int[] sn_list, int count, out int count_returned);
Turns out that when I called that, I got an access violation.
I tried searching for the solution, I even asked on StackOverflow, but no one seemed to have the answer in an easy-to-digest format.
So I did some digging. My first attempt used out int instead of the array. That didn't give me an exception, but it would only allow me to get one value out.
I eventually found the IntPtr type. My first attempt at using it looked something like this:
void Main()
{
var listP = Marshal.AllocHGlobal(20);
UP_GetProgList(2, listP, 20, out int countReturned);
for (int i = 0; i < countReturned; i++)
{
unsafe
{
Console.Out.WriteLine(*(int*)(listP + i));
}
}
}
[DllImport(@"the.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int UP_GetProgList(int prog_type, IntPtr sn_list, int count, out int count_returned);
That worked, and gave me the results I was expecting. There were two problems: 1. It used unsafe which I didn't like the look of, and, as a StackOverflow user pointed out, it had a memory leak.
Marshal.Copy, which I had looked at, but rejected because I'd only seen the version for copying an array into a pointer. Turns out there's an overload that does the opposite. Adding a try and finally block fixed the memory leak, so I ended up with the following code, which works, and shouldn't leak memory:
void Main()
{
Console.Out.WriteLine(GetSerialNumbers());
}
int[] GetSerialNumbers()
{
IntPtr listPtr = IntPtr.Zero;
try
{
listPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)) * 20);
var result = UP_GetProgList(PROG_FORTE, listPtr, 20, out int countReturned);
var sns = new int[countReturned];
Marshal.Copy(listPtr, sns, 0, countReturned);
return sns;
}
finally
{
Marshal.FreeHGlobal(listPtr);
}
}
This gave me the expected results, shouldn't leak memory, and also doesn't use unsafe. I hope this can serve as a template for someone else who encounters this problem.
Comments
Post a Comment