1

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??

Eran.G
  • 89
  • 1
  • 6
  • Not enough information to give a hint. For example, how do you know how many elements are in the arrays? Where exactly do you get the exception? – Steeeve Nov 01 '21 at 12:37
  • Thanks @Steeeve , I have added the missing information – Eran.G Nov 01 '21 at 12:58
  • It's still not enough. Your code would give a compile error because of not assigned variable `arraysLength`. Does the documentation says anything about how to use the function? Who is responsible for allocating the arrays? – Steeeve Nov 01 '21 at 13:07
  • No, the documentation says nothing about who is responsible for allocating the arrays. But maybe this is the problem and I need to allocate the memory, how do I do that? – Eran.G Nov 01 '21 at 13:12
  • Hm... then you can only try to allocate the arrays yourself and set arraysLength accordingly. It's just plain guessing without documentation. – Steeeve Nov 01 '21 at 13:13
  • Where do I need to put the allocation code? – Eran.G Nov 01 '21 at 13:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238768/discussion-between-eran-g-and-steeeve). – Eran.G Nov 01 '21 at 13:26
  • You want something like `int CPlusPlusFunction(int callbackArraysLength, [Out] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.FunctionPtr, SizeParamIndex = 0)] SolutionCallbackFuncDelegate[] solutionCallbackFunc);` Then you pre-assign an array buffer to pass in – Charlieface Nov 01 '21 at 13:39
  • First of all, are you sure the `#ifdef __WATCOMC__` branch is not taken? – GSerg Nov 01 '21 at 13:42
  • @Charlieface What is the point of wrapping the function pointer into an array? – GSerg Nov 01 '21 at 13:43
  • Does this answer your question? [Callback from Unmanaged code to managed](https://stackoverflow.com/questions/31511996/callback-from-unmanaged-code-to-managed) – GSerg Nov 01 '21 at 13:46
  • @GSerg I don't know when it uses the __WATCOMC__. I just know I'm running it from windows 10 and in my C# code, I didn't do anything to make sure it will use the _WINDOWS. – Eran.G Nov 01 '21 at 13:46
  • @Eran.G Are you compiling the DLL from source, or do you only have the header? If you only have the header, do you know whether your dll was compiled as cdecl or stdcall? – GSerg Nov 01 '21 at 13:48
  • @GSerg If I am not mistaken `int (*solutionCallbackFunction)(int*, double*, double)` is an array of function pointers, for which the array size is passed in `callbackArraysLength` – Charlieface Nov 01 '21 at 13:49
  • If `WATCOMC` is being used then you need `CallingConvention.StdCall` which happens to be the default anyway – Charlieface Nov 01 '21 at 13:50
  • @Charlieface [No](https://cdecl.org/?q=int+%28*solutionCallbackFunction%29%28int*%2C+double*%2C+double%29). – GSerg Nov 01 '21 at 13:50
  • @GSerg I cant tell, I only have the header. – Eran.G Nov 01 '21 at 13:50
  • Ask the vendor for more information. Find an example in C++ that calls the function. But trying to guess isn't productive, and certainly doesn't make a good question for this site. – David Heffernan Nov 02 '21 at 11:01

1 Answers1

0

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")]

Eran.G
  • 89
  • 1
  • 6