Introduction
Interoperability between the different languages in Visual Studio is cool feature, because it enhances posibilities and make code reuse easier. To demonstrate it I wrote some plain vanilla code which works.
Background
Some days ago somebody asked "how can I call C++ from C#" and I felt write a short article is good idea to condense my knowledge.
Using the code
The code consists of two parts: the C# code which consumes the C++ code from a dll. The C++ dll is some annoying code like using a C++ class with its function and some standard lib calls.
CPLUSPLUS_API int multiply(int v1, int v2)
{
Worker worker;
int res = worker.DoMultiply(v1,v2);
return res;
}
CPLUSPLUS_API int buildText(const char* s1, const char *s2, char* sResult, int len)
{
int cnt = _snprintf( sResult, len, "%s %s.", s1, s2 );
int rc = (len > cnt) ? cnt : -1;
return rc;
}
Interesting is that Strings and buffers are manipulated, but I stick to the rule that the memory comes from the client and no memory is allocated in the server code to be read in the server code. So the client needs to deliver the bytes, and the server works carefully with it.
CPLUSPLUS_API int buildBuffer(const unsigned char* s1, int l1, unsigned char* sResult, int len)
{
bool enoughBytes = (len >l1);
size_t lenCopy = enoughBytes ? l1 : len;
memcpy(sResult, s1, lenCopy);
return enoughBytes ? 0 : -1;
}
A final problem is working with callbacks which are functions and more precisly function pointers. In my sample only a function pointer is set and called. For more complex scenarios, the pointer should be only set and called back later in some threading scenarios.
CPLUSPLUS_API int setCallback( void* callback)
{
if( callback == NULL ) return -1;
VOID_CALLBACK_FUNCTION cbFunction = (VOID_CALLBACK_FUNCTION) callback;
cbFunction();
return 0;
}
More interesting is the C# side, in which I use the Interop services. In that I declare the the use of the dll function with its interface. If something crashes at most there is the cause. My real trick is to use Ansi characterset and the StringBuilder class which has a char[] operator or use a byte[]. And the callback is type defined in C# as a void delegate and has so the same "footprint" as the pointer is used in the dll.
using System.Runtime.InteropServices;
namespace CSharpInterOp
{
class CDllWrapper
{
#region Dll interface
public CDllWrapper() { }
[DllImport("CPlusPlus",
EntryPoint = "multiply",
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int multiply(int i1, int i2);
[DllImport("CPlusPlus",
EntryPoint = "buildText",
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int buildText(StringBuilder s1, StringBuilder s2, StringBuilder sResult, int len);
[DllImport("CPlusPlus",
EntryPoint = "buildBuffer",
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int buildBuffer(byte[] b1, int l1, byte[] bResult, int len);
[DllImport("CPlusPlus",
EntryPoint = "setCallback",
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int setCallback(Form1.callbackDelegate cb);
#endregion
}
}
Points of Interest
In the end looks it all clean and easy, but in this little code are some hard lessons. So I hope many will help it and the question "How to do this" are ending.
One of the learned lessons, is that "Sh*t happens" and some stable error handling is really need. If the dll is missing or cant be loaded because of other missing dlls you are right in the "DLL Hell". So be warned!!!
Finally I also want to point to my article Full power on the Phone which handles managed C++ with C# on Windows Phone 8, but could be some blueprint for the whole Windows platform.
History
Initial version.
Added simple callback functionality (5 dec 14)