Search code examples
c#pinvokemarshalling

Pinvoke - callback from C++, arrays passed between functions have unexpected size


EDIT: I've updated the code given the suggestions in @Hans Passant's comment and @David Heffernan's answer.

The argument c is no longer null, but both x and c still have length one when they are passed back to CallbackFunction.

I am trying to write C# code that passes a function pointer (using a delegate) to a C++ function, which calls the function pointer.

Code is below.

The problem I'm having is that when the C++ function f calls fnPtr(x,c), in the C# function CallbackFunction, x has one element (with the correct value of 1.0), and c is null. I have no idea what the problem is.

I can't change the signature of MyCallback.

C# code:

using System.Runtime.InteropServices;

namespace PInvokeTest
{
    class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate double MyCallback(
            [In] double[] x,
            [Out] double[] c);

        private static double CallbackFunction(
            [In] double[] x,
            [Out] double[] c)
        {
            c[0] = x[0] + x[1] + x[2];
            c[1] = x[0] * x[1] * x[2];
            return c[0] + c[1];
        }

        private static MyCallback _myCallback;

        [DllImport("NativeLib", CallingConvention = CallingConvention.StdCall)]
        private static extern int f(MyCallback cf);

        private static void Main()
        {
            _myCallback = new MyCallback(CallbackFunction);
            f(_myCallback);
        }
    }
}

NativeLib.h:

#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_

#ifndef MYAPI
  #define MYAPI 
#endif

#ifdef __cplusplus
extern "C"
{
#endif

#ifdef _WIN32
  #ifdef MAKE_MY_DLL
    #define MYAPI __declspec(dllexport) __stdcall
  #else
    #define MYAPI __stdcall
  #endif
#else
  #if __GNUC__ >= 4
    #define MYAPI __attribute__ ((visibility ("default")))
  #else
    #define MYAPI
  #endif
#endif

  typedef int MyCallback(const double * x,
    double * c);

  MYAPI int f(MyCallback * fnPtr);

#ifdef __cplusplus
}
#endif

#endif // _NATIVELIB_H_

NativeLib.cpp:

#include "NativeLib.h"
#include <stdio.h>
#include <malloc.h>

MYAPI int f(MyCallback * fnPtr)
{
  double x[] = { 1.0, 2.0, 3.0 };
  double c[] = { 0.0, 0.0, 0.0 };

  printf("%e\n", fnPtr(x, c));


  return 0;
}

Solution

  • Using ref on your array parameter is wrong. That's a spurious extra level of indirection. You also need to pass the array lengths as parameters and let the marshaller know these lengths.

    The delegate should be:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate double MyCallback(
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
        [In]  double[] x,
        [In]  int lenx,
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3)]
        [Out] double[] c,
        [In]  int lenc
    );
    

    Change CallbackFunction to match.