Let's say we have a C++ function with the following prototype:
int myFunction(int someNumber, int &arraySize, signed char *&array)
// Extra function to free allocated memory:
int freePointer(void* myPointer)
This function takes some number, and creates an array depending on that number. So we pass a number and get an array. What is the best way to call it in C#?
My first approach:
[DllImport(...)]
internal static int myFunction(int someNumber, out int arraySize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] out byte[] array)
// ...
byte[] myArray;
int discard;
int myNumber = 100;
myFunction(myNumber, discard, myArray);
But the problem is, I need to free the memory allocated by the unmanaged library, and I don't know how to pass the address of myArray
to freePointer()
. And also, I don't know if the address of myArray
is equal to the address of the array created by the unmanaged library.
And then my second approach was:
[DllImport(...)]
internal static int myFunction(int someNumber, out int arraySize, out IntPtr array)
// ...
byte[] myArray;
int myArraySize
IntPtr pointerToArray;
int myNumber = 100;
myFunction(myNumber, myArraySize, pointerToArray);
Marshal.Copy(pointerToArray, myArray, 0, myArraySize);
FreePointerHandler(pointerToArray.ToPointer());
But again, I'm not sure if I'm doing it right by sending the address of the IntPtr
using the method .ToPointer()
.
I don't want to introduce memory leaks, what would you suggest doing? (Without using unsafe code.)
Edit: Should I use ref
rather than out
, as the parameter is a reference to a pointer?
Your second approach looks about right to me. Where you have:
FreePointerHandler(pointerToArray.ToPointer());
I think you can just:
FreePointerHandler(pointerToArray);
I'm writing a test and will post an edit.
ref
vs. out
As far as ref
vs. out
, this doesn't map exactly to C++ reference. When C# call C#, an out
parameter is required to be set by the called function. With an an interop call this can't be enforced, for documentation value, I prefer out
in this case.
The real problem is if you don't have experience with .Net pinvoke, it's hard to have confidence when calling functions without any immediate, visible side effects.
@Hans Passant recommended "test it by calling it a million times" in order to make any memory leak apparent. While this approach is probably simplest in this case, it doesn't generalize to the case where there's not an associated memory resource and if it fails it doesn't give any additional information. I prefer to debug through the pinvoke call if I have the source code for the .dll or to write a mock API implementation if I don't. That way when you're trying out a new pinvoke feature you can see exactly what's going on. And if there's a problem, tracing in the debugger can help you see where you went wrong.
Here's a full example based on the original question. The return values were not documented, nor the details of myFunction. The example just fills an allocated array of length 3 with (0, 1, 2).
Here's the DLL's header file:
// testlib.h
#ifdef TESTLIB_EXPORTS
#define TESTLIB_API __declspec(dllexport)
#else
#define TESTLIB_API __declspec(dllimport)
#endif
extern "C" {
TESTLIB_API int myFunction(int someNumber, int &arraySize, signed char *&array);
TESTLIB_API int freePointer(void* myPointer);
}
The .DLL's .cpp source file:
// testlib.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "testlib.h"
#include <iostream>
using namespace std;
TESTLIB_API int myFunction(int someNumber, int &arraySize, signed char *&array)
{
#ifdef _DEBUG
cout << "myFunction enter " << someNumber << ", " << arraySize << ", 0x" << &array << endl;
#endif
arraySize = 3;
array = (signed char*)malloc(arraySize);
#ifdef _DEBUG
cout << "myFunction array: 0x" << (void *)array << endl;
#endif
for (int i = 0; i < arraySize; ++i)
{
array[i] = i;
}
#ifdef _DEBUG
cout << "myFunction exit " << someNumber << ", " << arraySize << ", 0x" << &array << endl;
#endif
return 0;
}
TESTLIB_API int freePointer(void* myPointer)
{
#ifdef _DEBUG
cout << "freePointer: 0x" << myPointer << endl;
#endif
free(myPointer);
return 0;
}
The C# source:
using System;
using System.Runtime.InteropServices;
namespace InteropTest
{
class InteropTest
{
[DllImport("testlib.dll")]
private static extern int myFunction(int someNumber, out int arraySize, out IntPtr array);
[DllImport("testlib.dll")]
public static extern int freePointer(IntPtr pointer);
public static byte[] myFunction(int myNumber)
{
int myArraySize;
IntPtr pointerToArray;
int iRet = myFunction(myNumber, out myArraySize, out pointerToArray);
// should check iRet and if needed throw exception
#if (DEBUG)
Console.WriteLine();
Console.WriteLine("InteropTest.myFunction myArraySize: {0}", myArraySize);
Console.WriteLine();
#endif
byte[] myArray = new byte[myArraySize];
Marshal.Copy(pointerToArray, myArray, 0, myArraySize);
freePointer(pointerToArray);
#if (DEBUG)
Console.WriteLine();
#endif
return myArray;
}
static void Main(string[] args)
{
#if (DEBUG)
Console.WriteLine("Start InteropTest");
Console.WriteLine();
#endif
int myNumber = 123;
byte[] myArray = myFunction(myNumber);
for (int i = 0; i < myArray.Length; ++i)
{
Console.WriteLine("InteropTest myArray[{0}] = {1}", i, myArray[i]);
}
}
}
}
The debug build prints status messages. They show myFunction
's array
value is what's passed to freePointer
.
I've also created an C# interface function for myFunction
. When a pinvoke call is non trivial, you usually want to work out the details once and encapsulate it into a function for reuse.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With