Search code examples
c#ccallbackmarshallingdllimport

Pass a callback function with arrays from C# to C DLL


I have a third-party C DLL, that came only with its header file as API. I want to be able to call one of its functions. The C header file looks like this:

#ifdef  __cplusplus
    extern "C" {
#endif
    
#ifdef __WATCOMC__
    #define EXPORT extern int __declspec(dllexport) __stdcall
    #define IMPORT extern int __declspec(dllimport) __stdcall
#endif
    
#ifdef _WINDOWS
#ifndef EXPORT
    #define EXPORT extern int
#endif
#endif
    
#ifndef EXPORT
    #define EXPORT int
#endif

#ifndef EXIMPORT
    #define EXIMPORT EXPORT
#endif
.
.
.
EXIMPORT CFunction(
  int callbackArraysLength,
  int (*solutionCallbackFunction)(int*, double*, double)
);

solutionCallbackFunction specification:

int *intArray
double *doubleArray
double doublefield

The C# code:

[DllImport(@"PathToDLL", EntryPoint = "CFunction", CallingConvention = CallingConvention.Cdecl)]
public static extern int CFunction(int callbackArraysLength, [MarshalAs(UnmanagedType.FunctionPtr)]  SolutionCallbackFuncDelegate solutionCallbackFunc);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int SolutionCallbackFuncDelegate ([In] int[] intArr, [In] double[] doubleArr,[In] double d);

public static int solutionFunction([In] int[] intArr, [In] double[] doubleArr,[In] double d) {
  .
  .
  // Some code that checks the input...
  .
  .
  return 0;
}

static SolutionCallbackFuncDelegate solCBDel;

public static void Main(string[] args)
{
  solCBDel = new SolutionCallbackFuncDelegate(solutionFunction);
  ...
  int arraysLength = 5; //I know the size of the arrays that will be returned from the callback function.
  int result = CFunction(arraysLength, solCBDel);
  ...
}

Have I done something wrong? I'm getting this error:

System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

and I'm not sure this is the reason.

I get the exception when calling the function inside the Main:

int result = CFunction(arraysLength, solCBDel);

The documentation doesn't say if I need to allocate the memory for the arrays in the callback function.
If this is the reason, where exactly do I allocate the arrays in the code?

Edit:

@GSerg suggested this answer: Callback from Unmanaged code to managed But in my callback function, no field has the size of the arrays: int (*solutionCallbackFunction)(int*, double*, double) The main function that sends the callback function as a pointer is sent with the size of the arrays:

int result = CFunction(arraysLength, solCBDel);

On the C# side, I need to allocate the memory.

Is there another way of allocating the callback function arrays if the C++ and C# has the length when I'm sending it to C++ through arraysLength??


Solution

  • After deep research, I found out how to call from C# to the C DLL.

    The C# code should be:

    [DllImport(@"PathToDLL", EntryPoint = "CFunction", CallingConvention = CallingConvention.Cdecl)]
    public static extern int CFunction(int callbackArraysLength, [MarshalAs(UnmanagedType.FunctionPtr)]  SolutionCallbackFuncDelegate solutionCallbackFunc);
    
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int SolutionCallbackFuncDelegate ([In] IntPtr intArrPtr, [In] IntPtr doubleArrPtr,[In] double d);
    
    public static int solutionFunction([In] IntPtr intArrPtr, [In] IntPtr doubleArrPtr,[In] double d) {
      
      int[] intArr = new int[arraysLength];
      double[] doubleArr = new double[arraysLength];
      Marshal.Copy(intArrPtr, intArr , 0, arraysLength);
      Marshal.Copy(doubleArrPtr, doubleArr, 0, arraysLength);
    
    
      // Some code that checks the input...
      .
      .
      return 0;
    }
        
    public static int arraysLength = 5; //I know the size of the arrays that will be returned from the callback function. It should be global.
        
    public static void Main(string[] args)
    {
      ...
      
      int result = CFunction(arraysLength, solutionFunction);
      ...
    }
    

    In the C# callback definition, we must use an IntPtr and then use Marshal.Copy(...) because the callback function doesn't have a parameter of length. In case there is a length parameter in the callback function we can use [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = "Index of the length parameter")]