Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling an unmanaged library function that takes a reference to a pointer

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?

like image 781
hattenn Avatar asked Feb 11 '13 16:02

hattenn


1 Answers

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

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.

A full pinvoke example

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.

like image 110
jimhark Avatar answered Nov 18 '22 22:11

jimhark