实践:C++平台迁移以及如何用C#做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 .
先说下我的开发环境,用的是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();
HANDLE threadHandle = CreateThread(0, 0, DetectProc, this, 0, &threadId);
CloseHandle(threadHandle);
threadHandle = INVALID_HANDLE_VALUE;
- #ifdef TRADITIONALDLL_EXPORTS
- #define TRADITIONALDLL_API __declspec(dllexport)
- #else
- #define TRADITIONALDLL_API __declspec(dllimport)
- #endif
- extern "C" {
- TRADITIONALDLL_API double GetDistance(Location, Location);
- TRADITIONALDLL_API void InitLocation(Location*);
- }
如上定义一个宏定义,在导出的C++动态链接库中,可以选择项目属性里添加预处理器定义TRADITIONALDLL_EXPORTS,也或者是在引用这个文件加上.而在引用这个动态链接库不做处理.
先看一个普通的函数从非托管C++到C++/CRL到C#相应流程,这个函数是传入一个设备ID,得到设备的所有工程,以及默认的工程ID.
- // C++
- TRADITIONALDLL_API int GetTestList(const unsigned long deviceId, char** confs, int& count, int& defaultID);
- TRADITIONALDLL_API int GetTestList(const unsigned long deviceId, char** confs, int& count, int& defaultID)
- {
- auto tests = PApi->Spider_GetTestList();
- count = tests->count;
- if (count > )
- {
- auto testNames = new char[ * count];
- memset(testNames, , * count);
- for (int i = ; i < count; i++)
- {
- memcpy(testNames + i * , tests->driveArray[i].TestName, );
- }
- *confs = testNames;
- defaultID = PApi->curModule->nDefaultID;
- return FUNC_SUCCESS;
- }
- spiderAPI->errorStr = NotConnecteDev;
- return ;
- }
- // managed C++
- bool GetTestList(const unsigned long deviceId, [Out]List <String^>^% testList, [Out]int% defalutID);
- bool DeviceController::GetTestList(const unsigned long deviceId, [Out]List <String^>^% testList, [Out]int% defalutID)
- {
- testList = gcnew List<String^>();
- char *nameBuffer = NULL;
- int testCount = ;
- int dID = ;
- ::GetTestList(deviceId, &nameBuffer, testCount, dID);
- defalutID = dID;
- if (nameBuffer != NULL && testCount > )
- {
- char testName[];
- memset(testName, , );
- for (int index = ; index < testCount; ++index)
- {
- memcpy(testName, nameBuffer + index * , );
- String^ str = gcnew String(testName);
- testList->Add(str);
- }
- return true;
- }
- return false;
- }
- //C#
- bool result = DeviceController.Instance.GetTestList(Device.Id, out testNames, out defaultID);
基本的传递如上,但是现在要求C#实时刷新设备转过来的数据,简单来说,就是C++里socket接收线程收到设备发送的数据,需要通知C#界面刷新.看需求,C#里的事件就能满足,但是是C++发送的消息,在这我们根据C++里的回调函数与托管代码里的事件结合来完成,去掉一些不必要的代码,主要过程如下.
- // C++
- typedef void (__stdcall *OnDataMessageRev)(const unsigned long deviceId, char* data, const int eventId,const int p0, const int p1,const int p2);
- class Module
- {
- OnDataMessageRev onDataRev;
- void didDataReceived();
- void SetDataMessageCallback(OnDataMessageRev callback);
- }
- void Module::SetDataMessageCallback(OnDataMessageRev callback)
- {
- onDataRev = callback;
- }
- void Module::didDataReceived()
- {
- switch (dataMsg.Msg.nEventID)
- {
- case DSP_DISPNEXT_OK:
- {
- if (onDataRev)
- onDataRev(this->deviceId, dataMsg.Data, dataMsg.Msg.nEventID, dataMsg.Msg.nParameters0, dataMsg.Msg.nParameters1, dataMsg.Msg.nParameters2);
- }
- break;
- //...
- }
- }
- DEVICEAPI_API void SetDataMessageCallback(OnDataMessageRev callback);
- DEVICEAPI_API void SetDataMessageCallback(OnDataMessageRev callback)
- {
- model.SetDataMessageCallback(callback);
- }
- // managed C++
- public delegate void DeviceDataMessageHandler(const unsigned long deviceId, const array<Byte>^ data, const int eventId, const int p0, const int p1, const int p2);
- public delegate void DeviceDataCallback(const unsigned long deviceId, char* data, const int eventId, const int p0, const int p1, const int p2);
- public ref class DeviceController
- {
- DeviceDataCallback^ dataCallback;
- DeviceDataMessageHandler^ onDeviceDataReceived;
- event DeviceDataMessageHandler^ DeviceDataReceived
- {
- void add(DeviceDataMessageHandler^ h)
- {
- onDeviceDataReceived += h;
- }
- void remove(DeviceDataMessageHandler^ h)
- {
- onDeviceDataReceived -= h;
- }
- }
- DeviceController::DeviceController()
- {
- dataCallback = gcnew DeviceDataCallback(&(DeviceController::DataReceivedCallback));
- IntPtr ptrData = Marshal::GetFunctionPointerForDelegate(dataCallback);
- ::SetDataMessageCallback(static_cast<OnDataMessageRev>(ptrData.ToPointer()));
- GC::KeepAlive(dataCallback);
- }
- void OnDeviceDataReceived(const unsigned long deviceId, const array<Byte>^ data, const int eventId, const int p0, const int p1, const int p2)
- {
- DeviceDataMessageHandler^ handler = onDeviceDataReceived;
- if (handler != nullptr)
- {
- handler(deviceId, data, eventId, p0, p1, p2);
- }
- }
- }
- //C#
- DeviceController.Instance.DeviceDataReceived += Instance_DeviceDataReceived;
- T ByteArrayToStructure<T>(byte[] bytes, IntPtr pin, int offset) where T : struct
- {
- try
- {
- return (T)Marshal.PtrToStructure(pin + offset, typeof(T));
- }
- catch (Exception e)
- {
- return default(T);
- }
- }
- private void Instance_DeviceDataReceived(uint deviceId, byte[] data, int eventId, int p0, int p1, int p2)
- {
- GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
- IntPtr pin = handle.AddrOfPinnedObject();
- int nCheckNum = ByteArrayToStructure<int>(data, pin, offset);
- DISPLAYPARAMS displayParams = ByteArrayToStructure<DISPLAYPARAMS>(data, pin, offset);
- VCSParamsDSP vcsPar = ByteArrayToStructure<VCSParamsDSP>(data, pin, offset);
- handle.Free();
- }
C++里的memcyp确实很好用,上段代码中,ByteArrayToStructure也能实现如memcyp一样的功能,先用GCHandle.Alloc选择Pinned生成CG不能回改的内存区域,就和C++申请内存一样,然后根据偏移量offset,把对应的字节转成我们需要的数据.C++里的char和C#里的byte是一样的,都是一个字节,这里不要搞错了,也和C++一样,记的清除申请的内存空间.
实践:C++平台迁移以及如何用C#做C++包装层的更多相关文章
- [转贴]实践:C++平台迁移以及如何用C#做C++包装层
终于有个C++ 如何调用C#类库的文章,收藏之 在前面,我们看过OpenTK与MOgre,这二个项目都是C#项目,但是他的实现都是C++.他们简单来说就是一个包装层.常见的包装方式有二种,一 种就是我 ...
- 大数据平台迁移实践 | Apache DolphinScheduler 在当贝大数据环境中的应用
大家下午好,我是来自当贝网络科技大数据平台的基础开发工程师 王昱翔,感谢社区的邀请来参与这次分享,关于 Apache DolphinScheduler 在当贝网络科技大数据环境中的应用. 本次演讲主要 ...
- Windows C盘格式化或者同平台迁移oracle数据库
我们知道如果是Linux 同平台迁移oracle数据库.只要是安全关闭了数据库,在新机器上创建用户组,配置了环境变量,将数据库安装目录拷贝到对应的目录就好用了. 一直在寻求Windows平台上这类的解 ...
- Linux 工程向 Windows 平台迁移的一些小小 tips
Linux 工程向 Windows 平台迁移的一些小小 tips VS2013 C++11 Visual Studio 2013 没有做到对 C++11 所有的支持,其中存在的一个特性就是 In-cl ...
- 代码从windows下visual studio到andriod平台迁移的修改记录
前言 前言也是迁言,从windows的visual studio 2012平台迁移到android平台上,需用修改挺多的代码和需用注意地方. 我们当然的平台当初就考虑了其他平台跨平台的应用问题,所以一 ...
- [Linux实践] macOS平台Homebrew更新brew update卡死,完美解决
[Linux实践] macOS 平台 Homebrew 更新 brew update 卡死,完美解决 版本2020.01.05 摘要: 使用brew install [软件包]安装软件包时,卡在Upd ...
- 使用 Velero 跨云平台迁移集群资源到 TKE
概述 Velero 是一个非常强大的开源工具,可以安全地备份和还原,执行灾难恢复以及迁移Kubernetes群集资源和持久卷,可以在 TKE 平台上使用 Velero 备份.还原和迁移集群资源,关于如 ...
- 号外号外:9月13号《Speed-BI云平台案例实操--十分钟做报表》开讲了
引言:如何快速分析纷繁复杂的数据?如何快速做出老板满意的报表?如何快速将Speed-BI云平台运用到实际场景中? 本课程将通过各行各业案例背景,将Speed-BI云平台运用到实际场景中 ...
- 【CSS】如何用css做一个爱心
摘要:HTML的标签都比较简单,入门非常的迅速,但是CSS是一个需要我们深度挖掘的东西,里面的很多样式属性掌握几个常用的便可以实现很好看的效果,下面我便教大家如何用CSS做一个爱心. 前期预备知识: ...
随机推荐
- django——文件上传_分页_ajax_富文本_celery
上传文件 概述 当Django在处理文件上传时,文件的数据被存储在request.FILES属性中 FILES只有在请求的方法为POST且提交的form表单带有enctype="multip ...
- HTML5学习笔记(二十二):DOM
DOM即文档对象模型(Document Object Model),其定义了访问和操作 HTML 文档的标准方法. DOM 将 HTML 文档表达为树结构,如下: 通过DOM,开发人员可以动态的添加. ...
- 菜鸟学Java(四)——JSP内置对象
学习JavaWeb就离不开JSP,而学习JSP又不得不了解它常用的九个内置对象.今天来做一个简单介绍. request Request封装了用户提交的信息,通过调用Request相应的方法可以获取封装 ...
- [SQL Server 2014] SQL Server 2014新特性探秘
SQL Server 2014新特性探秘(1)-内存数据库 简介 SQL Server 2014提供了众多激动人心的新功能,但其中我想最让人期待的特性之一就要算内存数据库了.去年我再西雅图参加SQ ...
- 每日英语:Got 5 Minutes? 'Flash Fiction' Catches On
Chinese author Lao Ma has a simple approach to his short stories: In the face of life, everything is ...
- 每日英语:Redfin Real-Estate Firm Gets Cold Shoulder in Silicon Valley
"I used to think I was this made man," says entrepreneur Glenn Kelman. "That's what t ...
- tensorflow笔记2:TensorBoard
Tensorboard中的参数 Summary:所有需要在TensorBoard上展示的统计结果. tf.name_scope():为Graph中的Tensor添加层级,TensorBoard会按照代 ...
- Solr实现 并集式、多值、复杂 过滤查询的权限【转】
公司开发使用的搜索引擎核心是Solr,但是应为业务原因,需要相对复杂权限机制. 1)通过Solr的filterQuery可以实现field过滤,实现过滤项的效果.索引A{filter1:a,field ...
- Office_Visio_Pro_2007
Office_Visio_Pro_2007http://pan.baidu.com/share/link?shareid=473782&uk=3474501992 解压后在文件夹里找到密钥[一 ...
- Spring事务管理要点总结
# Spring事务管理要点总结 ### 要点---- 事务是企业级应用中必不可缺少的技术,用来确保数据的完整性和一致性.- Spring事务管理并不实现事务管理的实现,而是借助Hibernate\J ...