在前面,我們看過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;

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 }
如上定義一個宏定義,在導出的C++動態鏈接庫中,可以選擇項目屬性里添加預處理器定義TRADITIONALDLL_EXPORTS,也或者是在引用這個文件加上.而在引用這個動態鏈接庫不做處理.
先看一個普通的函數從非托管C++到C++/CRL到C#相應流程,這個函數是傳入一個設備ID,得到設備的所有工程,以及默認的工程ID.

1 // C++ 2 TRADITIONALDLL_API int GetTestList(const unsigned long deviceId, char** confs, int& count, int& defaultID); 3 TRADITIONALDLL_API int GetTestList(const unsigned long deviceId, char** confs, int& count, int& defaultID) 4 { 5 auto tests = PApi->Spider_GetTestList(); 6 count = tests->count; 7 if (count > 0) 8 { 9 auto testNames = new char[32 * count]; 10 memset(testNames, 0, 32 * count); 11 for (int i = 0; i < count; i++) 12 { 13 memcpy(testNames + i * 32, tests->driveArray[i].TestName, 32); 14 } 15 *confs = testNames; 16 defaultID = PApi->curModule->nDefaultID; 17 return FUNC_SUCCESS; 18 } 19 spiderAPI->errorStr = NotConnecteDev; 20 return 0; 21 } 22 // managed C++ 23 bool GetTestList(const unsigned long deviceId, [Out]List <String^>^% testList, [Out]int% defalutID); 24 bool DeviceController::GetTestList(const unsigned long deviceId, [Out]List <String^>^% testList, [Out]int% defalutID) 25 { 26 testList = gcnew List<String^>(); 27 char *nameBuffer = NULL; 28 int testCount = 0; 29 int dID = 0; 30 ::GetTestList(deviceId, &nameBuffer, testCount, dID); 31 defalutID = dID; 32 if (nameBuffer != NULL && testCount > 0) 33 { 34 char testName[32]; 35 memset(testName, 0, 32); 36 for (int index = 0; index < testCount; ++index) 37 { 38 memcpy(testName, nameBuffer + index * 32, 32); 39 String^ str = gcnew String(testName); 40 testList->Add(str); 41 } 42 return true; 43 } 44 return false; 45 } 46 //C# 47 bool result = DeviceController.Instance.GetTestList(Device.Id, out testNames, out defaultID);
基本的傳遞如上,但是現在要求C#實時刷新設備轉過來的數據,簡單來說,就是C++里socket接收線程收到設備發送的數據,需要通知C#界面刷新.看需求,C#里的事件就能滿足,但是是C++發送的消息,在這我們根據C++里的回調函數與托管代碼里的事件結合來完成,去掉一些不必要的代碼,主要過程如下.
1 // C++ 2 typedef void (__stdcall *OnDataMessageRev)(const unsigned long deviceId, char* data, const int eventId,const int p0, const int p1,const int p2); 3 4 class Module 5 { 6 OnDataMessageRev onDataRev; 7 void didDataReceived(); 8 void SetDataMessageCallback(OnDataMessageRev callback); 9 } 10 void Module::SetDataMessageCallback(OnDataMessageRev callback) 11 { 12 onDataRev = callback; 13 } 14 void Module::didDataReceived() 15 { 16 switch (dataMsg.Msg.nEventID) 17 { 18 case DSP_DISPNEXT_OK: 19 { 20 if (onDataRev) 21 onDataRev(this->deviceId, dataMsg.Data, dataMsg.Msg.nEventID, dataMsg.Msg.nParameters0, dataMsg.Msg.nParameters1, dataMsg.Msg.nParameters2); 22 } 23 break; 24 //... 25 } 26 } 27 DEVICEAPI_API void SetDataMessageCallback(OnDataMessageRev callback); 28 DEVICEAPI_API void SetDataMessageCallback(OnDataMessageRev callback) 29 { 30 model.SetDataMessageCallback(callback); 31 } 32 // managed C++ 33 public delegate void DeviceDataMessageHandler(const unsigned long deviceId, const array<Byte>^ data, const int eventId, const int p0, const int p1, const int p2); 34 public delegate void DeviceDataCallback(const unsigned long deviceId, char* data, const int eventId, const int p0, const int p1, const int p2); 35 public ref class DeviceController 36 { 37 DeviceDataCallback^ dataCallback; 38 DeviceDataMessageHandler^ onDeviceDataReceived; 39 event DeviceDataMessageHandler^ DeviceDataReceived 40 { 41 void add(DeviceDataMessageHandler^ h) 42 { 43 onDeviceDataReceived += h; 44 } 45 void remove(DeviceDataMessageHandler^ h) 46 { 47 onDeviceDataReceived -= h; 48 } 49 } 50 51 DeviceController::DeviceController() 52 { 53 dataCallback = gcnew DeviceDataCallback(&(DeviceController::DataReceivedCallback)); 54 IntPtr ptrData = Marshal::GetFunctionPointerForDelegate(dataCallback); 55 56 ::SetDataMessageCallback(static_cast<OnDataMessageRev>(ptrData.ToPointer())); 57 GC::KeepAlive(dataCallback); 58 } 59 60 void OnDeviceDataReceived(const unsigned long deviceId, const array<Byte>^ data, const int eventId, const int p0, const int p1, const int p2) 61 { 62 DeviceDataMessageHandler^ handler = onDeviceDataReceived; 63 if (handler != nullptr) 64 { 65 handler(deviceId, data, eventId, p0, p1, p2); 66 } 67 } 68 } 69 70 //C# 71 72 DeviceController.Instance.DeviceDataReceived += Instance_DeviceDataReceived; 73 74 T ByteArrayToStructure<T>(byte[] bytes, IntPtr pin, int offset) where T : struct 75 { 76 try 77 { 78 return (T)Marshal.PtrToStructure(pin + offset, typeof(T)); 79 } 80 catch (Exception e) 81 { 82 return default(T); 83 } 84 } 85 private void Instance_DeviceDataReceived(uint deviceId, byte[] data, int eventId, int p0, int p1, int p2) 86 { 87 GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); 88 IntPtr pin = handle.AddrOfPinnedObject(); 89 int nCheckNum = ByteArrayToStructure<int>(data, pin, offset); 90 DISPLAYPARAMS displayParams = ByteArrayToStructure<DISPLAYPARAMS>(data, pin, offset); 91 VCSParamsDSP vcsPar = ByteArrayToStructure<VCSParamsDSP>(data, pin, offset); 92 handle.Free(); 93 94 }
View Code
C++里的memcyp確實很好用,上段代碼中,ByteArrayToStructure也能實現如memcyp一樣的功能,先用GCHandle.Alloc選擇Pinned生成CG不能回改的內存區域,就和C++申請內存一樣,然后根據偏移量offset,把對應的字節轉成我們需要的數據.C++里的char和C#里的byte是一樣的,都是一個字節,這里不要搞錯了,也和C++一樣,記的清除申請的內存空間.