0

I am writing a C# API to a C++ library that takes a function pointer with this signature:

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

Array x has size n, array c has size m, and array l has size m + n.

I cannot change the signature of the C++ MyCallback function.

The question:

I've passed the arrays from C# using MarshalAs(UnmanagedType.LPArray, SizeParamIndex = <approriate_index>). this is fine for x and c. How do I marshal l and keep track of its size, when I don't explicitly have a parameter with its size?

The only solution I know of that works is to pass IntPtr from C# and use unsafe and ToPointer(). Is there a another solution?

An example of what I'm trying to do is below:

C# code:

using System.Runtime.InteropServices;

namespace PInvokeTest
{
    public class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate double MyCallback(
            int n,
            int m,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x,
            [In, MarshalAs(UnmanagedType.LPArray)] double[] l,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] double[] c);

        private static double CallbackFunction(
            int n,
            int m,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x,
            [In, MarshalAs(UnmanagedType.LPArray)] double[] l,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] double[] c)
        {
            // THIS IS WILL NOT WORK, l HAS SIZE 1.
            // In this example, only l[0] and l[1] are used, but in general
            // the size of l is n + m.
            c[0] = x[0] + x[1] + x[2] + l[0]*l[1];
            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 = CallbackFunction;
            f(_myCallback);
        }
    }
}

Header file:

#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_

#ifndef MYAPI
  #define MYAPI 
#endif

#ifdef __cplusplus
extern "C"
{
#endif

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

  MYAPI int f(MyCallback * fnPtr);

#ifdef __cplusplus
}
#endif

#endif // _NATIVELIB_H_

C++ source:

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

MYAPI int f(MyCallback * fnPtr)
{
  int n = 3;
  int m = 2;

  double x[] = { 1.0, 2.0, 3.0 };
  double l[] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
  double c[] = { 0.0, 0.0};

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

  printf("the value of c after the function call:\n");

  printf("%e %e\n", c[0], c[1]);
  return 0;
}
user327301
  • 2,641
  • 2
  • 26
  • 33
  • I may well have misunderstood something, but I'm wondering why you say "I've passed the arrays _from_ C# using ..." and "... is to pass IntPtr _from_ C# and ...". Don't you mean _to_ C#? – RenniePet Dec 28 '14 at 04:12
  • Anyway, would it be feasible for you to write a very thin C++ wrapper for the C++ library, where you define a callback function that conforms to the MyCallback signature, and then it invokes a slightly more marshalling-friendly callback function with an extra parameter that contains the length of the l array? – RenniePet Dec 28 '14 at 04:16
  • Yes you're right, I guess it should be "to". They're coming from C++ as pointers and to C# as whatever I'm marshalling them as, but I wrote 'from' because I was defining how I was passing them to C# in terms of C#. Anyway... – user327301 Dec 28 '14 at 04:20
  • About the second comment. The People In Charge want the C# interface to consist of "only .cs files". It would be easier to create a C++ wrapper, but I want avoid using one before trying to convince them that I need one. – user327301 Dec 28 '14 at 04:23
  • Not that I'd recommend cheating, (oh, no, not me), but if it comes to that then try doing a Google search for "c# dll as embedded resource". For example: http://stackoverflow.com/questions/72264/how-can-a-c-windows-dll-be-merged-into-a-c-sharp-application-exe – RenniePet Dec 28 '14 at 04:49

1 Answers1

3

You'd need a custom marshaller for this callback. But there is little point, you can simply marshal the array yourself:

private static double CallbackFunction(..., IntPtr lptr, ...) {
    var l = new double[n + m];
    Marshal.Copy(lptr, l, 0, l.Length);
    // etc...
}

Update the delegate signature to match.

Do note that all the arrays are getting copied. That is pretty expensive, you may well favor using pointers so no copying is required:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate double MyCallback(int n, int m, double* x, double* y, double* c);

Update CallbackFunction() to match and use Project + Properties, Build tab, tick "Allow unsafe code". This tends to invoke the Eek! response, there just aren't that many ways to fumble the code and write beyond the array boundaries if your snippet is a decent match for the real code. Pick good names for these arguments to avoid the other eek.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • To be clear, when you say that all the arrays are getting copied in the first solution you presented, you mean arrays x, c, AND l, and not just l? – user327301 Dec 29 '14 at 18:40
  • All of them, the underlying native arrays are unmanaged so a copy must be made. And the reason the pinvoke marshaller needs to know the size. A copy can only be avoided if native code consumes a managed array, the non-reverse pinvoke way. – Hans Passant Dec 29 '14 at 18:45