呼叫所有站点
互操作性:从 C# 调用 C++
引言
Visual Studio 中不同语言之间的互操作性是一项很棒的功能,因为它增强了可能性并使代码重用更容易。为了演示这一点,我编写了一些简单的代码,它可以正常工作。
背景
几天前,有人问“如何从 C# 调用 C++”,我觉得写一篇简短的文章来总结我的知识是个好主意。
使用代码
代码分为两部分:C# 代码,它从 DLL 中使用 C++ 代码。C++ DLL 包含一些令人讨厌的代码,例如使用带有其函数和一些标准库调用的 C++ 类。
CPLUSPLUS_API int multiply(int v1, int v2)
{
//do the heavy lifting with C++
Worker worker;
int res = worker.DoMultiply(v1,v2);
//finally return the result, or error code !!!
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;
}
有趣的是,字符串和缓冲区被操作,但我坚持一个规则,即内存来自客户端,并且服务器代码中不分配任何内存以供服务器代码读取。因此,客户端需要提供字节,服务器小心地处理它们。
CPLUSPLUS_API int buildBuffer(const unsigned char* s1, int l1, unsigned char* sResult, int len)
{
//some lite error handling
bool enoughBytes = (len >l1);
//only copy buffersize
size_t lenCopy = enoughBytes ? l1 : len;
memcpy(sResult, s1, lenCopy);
return enoughBytes ? 0 : -1;
}
最终的问题是处理回调,这些回调是函数,更准确地说,是函数指针。在我的示例中,仅设置并调用一个函数指针。对于更复杂的场景,应该仅设置指针,并在某些线程场景中稍后回调它。
CPLUSPLUS_API int setCallback( void* callback)
{
if( callback == NULL ) return -1;
VOID_CALLBACK_FUNCTION cbFunction = (VOID_CALLBACK_FUNCTION) callback;
//simple callback
cbFunction();
return 0;
}
更有趣的是 C# 方面,我使用了 Interop 服务。 在其中,我声明了对 DLL 函数及其接口的使用。如果出现任何崩溃,原因通常就在这里。我的真正技巧是使用 Ansi 字符集和 StringBuilder 类,该类具有 char[] 运算符或使用 byte[]。回调在 C# 中定义为 void 委托,因此与 DLL 中使用的指针具有相同的“足迹”。
//for dll imports we need that
using System.Runtime.InteropServices;
// this class manages the calls to the dll
namespace CSharpInterOp
{
class CDllWrapper
{
#region Dll interface
public CDllWrapper() { }
[DllImport("CPlusPlus", //name of the dll
EntryPoint = "multiply", //name of function in dll
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int multiply(int i1, int i2);
[DllImport("CPlusPlus", //name of the dll
EntryPoint = "buildText", //name of function in dll
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int buildText(StringBuilder s1, StringBuilder s2, StringBuilder sResult, int len);
[DllImport("CPlusPlus", //name of the dll
EntryPoint = "buildBuffer", //name of function in dll
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int buildBuffer(byte[] b1, int l1, byte[] bResult, int len);
[DllImport("CPlusPlus", //name of the dll
EntryPoint = "setCallback", //name of function in dll
ExactSpelling = true,
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int setCallback(Form1.callbackDelegate cb);
#endregion
}
}
关注点
最终看起来一切都干净而简单,但在这段小代码中包含了一些重要的经验教训。所以我希望很多人能从中受益,并结束“如何做到这一点”的问题。
其中一个学到的教训是,“事无巨细”并且确实需要稳定的错误处理。如果 DLL 丢失或由于其他丢失的 DLL 无法加载,您就陷入了“DLL 地狱”。所以要小心!
最后,我还想指出我的文章 Full power on the Phone,它处理了 Windows Phone 8 上 C++ 与 C# 的托管 C++,但可以作为整个 Windows 平台的蓝图。
历史
初始版本。
添加了简单的回调功能 (12 月 5 日)