实践:C++平台迁移以及如何用C#做C++包装层
作者:网络转载 发布时间:[ 2014/8/5 10:19:29 ] 推荐标签:C++ 平台迁移
在前面,我们看过OpenTK与MOgre,这二个项目都是C#项目,但是他的实现都是C++.他们简单来说是一个包装层.常见的包装方式有二种,一种是我们熟知的显式P/Invoke(DllImport),上面所说的OpenTK是这种,还有一种是C++ -> C++/CRL -> C#,这种也叫隐式P/Invoke,也有称C++ Interop,MOgre是采用的这种方式.在这篇文章主要讲的是隐式P/Invoke,具体相关操作请见http://msdn.microsoft.com/zh-cn/library/2x8kf7zx.aspx .
先说下这个项目的需求,有二个C++项目,都是和信号收集设备打交通,一个是在window环境下实现了设备的实时模式,另外一个项目是在IOS环境下写的设备的黑盒模式.现在要求是把IOS环境下写的代码整合到第一个项目window环境下,然后再提供C#层面的包装接口与一个简单运行的DEOM.需求很简单,主要是我从离开大学一直玩的是C#,C++好久没用过,如果以下说的有错误的地方,欢迎大家指出,我会马上修改.同时也谢谢公司给我的这次实践,从开始自学游戏开发(OpenGL为主)开始,面对全是C++代码,虽然读与翻译成别的类型语言都能做到,是一直没机会系统的用上C++,这次算是对C++项目的初级改动.
先说下我的开发环境,用的是VS2013,如下问题有些版本可能不一样.
从IOS环境下的代码,移到vs2013中,首先是如sprintf这种都会报错,因为vs2013会默认启用安全处理,sprintf的安全版本是sprintf_s,会加上缓冲区检查,还有一些如strcpy,strcpy_s,localtime,localtime_s.一般提示你启用安全版本,在对应的函数后添加_s,参数个数可能也会变,不过根据定义都容易改.
然后是socket里的一些原型参数的区别了,在IOS或是LINUX里,如关闭socket直接是close,在vs2013中是closesocket,ioctl与ioctlsocket也是如此,如果编译出错,又知道是socket里的函数,可以尝试在原函数后加上socket,然后是查询广播这个地方二个位置的写法差别比较大,在原IOS环境下ioctl传入SIOCGIFCONF命令,得到对应的ifconf数组,然后把ifconf数组每个值通过ioctl传入SIOCGIFBRDADDR命令得到对应的广播地址,我开始尝试把其中的一些ifconf结构引用进来,后来发现关联的比较多,现在想了一想,是全添加进来,应该也是不行的.而在window环境下,直接使用WSAIoctl,传入命令SIO_GET_INTERFACE_LIST得到INTERFACE_INFO的结构体,里面包含了所需要的广播地址,别的区别不大了,相关的很多API是通用的.
到这里,差不多原来的IOS环境下的代码转到VS2013中了,然后是把这部分代码整合到原来的实时模式下,因为二个项目有些头文件本来有重合的,或者重合的结构体,但是有些属性名不一样,二个相同功能的类,但是部分API与成员不一样的问题,在这我的办法是先把原IOS环境下的代码大致看一下,确定引用关系前后,是没引用项目内别的文件与引用项目内别的文件多的文件的.h与.cpp文件,先把引用关系小的.h与点.cpp文件转移过去,然后确保能正确编译,然后再按顺序,一个个移过来,对于高手不清楚,但是对于我们这种小白,能保证不会因大堆错误而烦心,每转移一个文件成功后都会感觉到高兴.
在这,实时模式和IOS下的黑盒模式代码都成功整合到VS2013中了,这里遇到一个问题.如下代码 所示.
std::thread detectThread(&PApi::DetectProc, this);
detectThread.detach();
在原IOS环境下用的线程都是用的STL下的线程类,在主线程下,初始化一个类中包含如上STL线程类的初始化,会卡在初始化那里,根本进入不了detach这个方法,开始是单独重建一个函数,包含上面的代码,在初始化后调用这个函数没有问题.但是这个功能本来应该是在初始化启用的,再初始化后再加一函数,感觉不好,故把相关线程改成window api里的线程启用.问题解决,代码如下,需要把成员函数DetectProc改成静态成员函数.
DWORD threadId = 0;
HANDLE threadHandle = CreateThread(0, 0, DetectProc, this, 0, &threadId);
CloseHandle(threadHandle);
threadHandle = INVALID_HANDLE_VALUE;
这是一个小插曲,问题解决后,我们按要求转移到C#平台.如前面所说,采用的是隐式P/Invoke,我们来看看是如何实现的.
我们用C++实现全局钩子的同学们都知道,非托管C++DLL里的函数不同C#托管DLL,你在里面写个Public,引用这个DLL的项目能看到,非托管C++动态链接库想让外面知道内部的API,需要添加关键字__declspec(dllexport).而不管是显示P/Invoke还是隐式P/Invoke引用非托管C++里的API,都需要相关API声明成__declspec(dllexport).
在这里,说下P/Invoke显示调用与隐式调用的用法的不同,显示调用一般是在C#中使用DLLImport特性包装非托管语言用关键字__declspec(dllexport)公开的API.而隐式调用一般会新建一个托管C++项目,也是C++/CRL,托管C++和非托管C++共同点是.h头文件.cpp源文件分类与头文件引用这些,别的部分可能让我感觉更像是C#,通过托管C++生成的动态链接库和我们C#生成DLL一样,直接能被托管项目引用调用类啥的,而托管C++也能像非托管C++一样,引用头文件直接调用相应API,不需要用DLLImport,但其实总的来说更麻烦些,不过能实现的功能也多些,有些C++好用的方法在C#里没有,如memcyp,一些针对指针的操作也和C++一样方便.
__declspec(dllexport)是我们暴露给外部的DLL生成API所需的,对应我们用P/Invoke引用这些公开的API时,好在对应的方法上给出关键字__declspec(dllimport),虽然导出函数不是必要的,但是如果是DLL中的变量,这个是必需的,并且编译器也能针对关键字做特定优化.下面是一段常见代码.
1 #ifdef TRADITIONALDLL_EXPORTS
2 #define TRADITIONALDLL_API __declspec(dllexport)
3 #else
4 #define TRADITIONALDLL_API __declspec(dllimport)
5 #endif
6
7 extern "C" {
8 TRADITIONALDLL_API double GetDistance(Location, Location);
9 TRADITIONALDLL_API void InitLocation(Location*);
10 }
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11