3

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;
}
3
  • The delegate needs [UnmanagedFunctionPointer(CallingConvention.Cdecl)]. Remove ref from the c argument, give it [In, Out] Commented Dec 25, 2014 at 18:48
  • Can anyone comment on why my question has -1 votes? Maybe it's a stupid question (as in, the solution is very easy), but I was careful to include a complete and small example code and tried to ask a specific question. I'm disappointed. Commented Dec 26, 2014 at 17:33
  • @user327301 maybe the reason is the question doesn't emphasize the core question, it's not easy to understand the question with a glance. Commented Feb 14, 2020 at 6:16

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

4 Comments

About the "CallingConvention.Cdecl": I edited the C++ code to include what is actually in the header (which I can't change). Given the __declspec(dllexport) __stdcall in the header, should it be CallingConvention.Cdecl?
Well, given the new information then yes, the function exported by the DLL is stdcall. That information is new and clearly we can only answer based on the information provided. The callback is cdecl though. Unless there's more information that you did not share.
Of course, I can't expect the answer to be guessed correctly without all of the relevant information. I'm sorry.
The calling convention is really a side issue. You can see the actual code and know the conventions used. I don't think there's more to say here.
0

The "duh" assumption I was making was that C would somehow pass some information on the size of x and c arrays, when it's just passing pointers. The solution that I found was to mimic the code found in a related SO question. (I don't know if it's a good way to solve my problem, but it works).

I changed the callback function (and the delegate definition to match) to use an IntPtr. In general, I obviously need to pass information on the sizes of c and x.

unsafe private static double CallbackFunction(
  [In] IntPtr xp,
  [Out] IntPtr cp)
  {
    Double* c = (Double*) cp.ToPointer();
    Double* x = (Double*) xp.ToPointer();
    c[0] = x[0] + x[1] + x[2];
    c[1] = x[0] * x[1] * x[2];
    return c[0] + c[1];
  }

5 Comments

No need to use unsafe. Far better would be to pass the length of the arrays in extra args so that they could be marshalled as arrays.
I'm not understanding how to do that. When CallbackFunction took a double[] as an argument, the double[] x in CallbackFunction had x.Length = 1. Regardless of whether I pass the array length as an extra argument or not, how do I access beyond x[0] if the length of x is only 1?
You'd use MarshalAs(UnmanagedType.LPArray) and then SizeParamIndex. I think. That would let the pinvoke marshaller marshal to and from the array. I think that out to work with reverse p/invoke.
Thank you! That did it. If you make answer I can accept it and delete this bad answer.
I resuscitated my original answer. Does that suffice? Please feel free to fix any errors in my code. I'm writing it blind.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.