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