65.9K
CodeProject 正在变化。 阅读更多。
Home

呼叫所有站点

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.35/5 (11投票s)

2014年11月18日

CPOL

2分钟阅读

viewsIcon

43497

downloadIcon

931

互操作性:从 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 日)

© . All rights reserved.