For the record, I got most of the way to getting this working, then ended up pulling the appropriate C files into the C++ project (because I was fighting with compatibility issues in code that I didn't even need).
Here are some tips I picked up along the way which may be helpful for people happening on this question:
Marshalling arrays
In C, there's no difference between a pointer to double (double*
) and an array of doubles (double*
). When you come to interop, you need to be able to disambiguate. I needed to pass arrays of double
, so the signature might look like this:
[DllImport(@"PathToDll")]
public static extern Foo(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)[In] double[] x,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)[Out] double[] y,
int x_size,
int y_size)
C needs the extra information of how long the array is, which you have to pass in as a separate parameter. The marshalling also needs to know where this size is, so you specify the SizeParamIndex
, which indicates the zero-based index of the size parameter in the list of parameters.
You also specify which direction the array is intended to be passed. In this example x
is passed into Foo
, which 'sends back' y
.
Calling convention
You don't actually need to understand the finer details of what this means (in other words, I don't), you just need to know that different calling conventions exist, and that they need to match on both sides. The C# default is StdCall
, the C default is Cdecl
. This means that you only need to explicitly specify the calling convention if it differs from the default on which ever side you're using it.
This is particularly hairy in the case of a callback. If we're passing a callback to C
from C#
, we're intending for that callback to be invoked with StdCall
, but when we pass it in, we're using Cdecl
. This results in the following signatures (see this question for context):
//=======C-code======
//type signature of callback function
typedef int (__stdcall *FuncCallBack)(int, int);
void SetWrappedCallback(FuncCallBack); //here default = __cdecl
//======C# code======
public delegate int FuncCallBack(int a, int b); // here default = StdCall
[DllImport(@"PathToDll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetWrappedCallback(FuncCallBack func);
Packaging the callback
Obvious, but it wasn't immediately obvious to me:
int MyFunc(int a, int b)
{
return a * b;
}
//...
FuncCallBack ptr = new FuncCallBack(MyFunc);
SetWrappedCallback(ptr);
.def file
Any functions you want to expose from C++ project (to be DllImport
ed), need to figure in the ModDef.def
file, who's contents would look something like this:
LIBRARY MyLibName
EXPORTS
SetWrappedCallback @1
extern "C"
If you want to use C functions from C++, you have to declare them as extern "C"
. If you are including a header file of C functions, you go like this:
extern "C" {
#include "C_declarations.h"
}
Precompiled headers
Another thing I had to do to avoid compilation errors was Right-click -> Properties -> C/C++ -> Precompiled Headers
and set Precompiled header
to Not Using Precompiled Headers for each 'C' file.