CSharp調用C++編寫的DLL的方法

本身比較懶,有的時候想寫點東西,但因爲文筆不行、技術不行也就沒有怎麼寫。常常是用到什麼、學習什麼的時候,簡單寫點,權當是個學習筆記。上博客的次數也不多,有人給我留言也是沒有怎麼及時的回覆,深感抱歉!c++

在一些特殊的行業,好比我從事的GIS、地質行業,大部分軟件仍是以C/S形式存在,軟件大可能是產品來銷售。這些程序大部分是Cpp語言來編寫,一方面是考慮到效率問題,另外一方面多是由於歷史緣由,建立者使用Cpp,後面接班人也就繼續使用。編程

可是使用Cpp去作項目的時候,又會倍感cpp的笨拙,作個界面很是費勁。因此若是可以使用C#語言來研發,使用WinForm、WPF來作界面,世界就會美好不少。但是軟件產品生成的不少成果想要利用起來就比較困難,用C#從新寫一遍系統是一條很好的路,技術難度低,可是工做量大,後期維護也比較困難,最主要的是在項目實施過程當中,時間不夠。另一種思路就是對現有的Cpp系統進行包裝,直接用C#調用,這幾天比較了幾種方法,最後使用CLR對C++進行封裝了,可行性比較高。數組

一). PInvoke服務器

不須要修改C++的DLL,直接在C#程序中把須要的接口引進進來便可。開始的時候感受比較順暢,可是後面越搞越麻煩,在CSharp和Cpp之間傳遞的個數組、傳遞個類,須要編寫不少,而且MS上的文檔也看得暈乎乎的。最後就放棄了。網絡

1. 首先建立一個C++的普通DLL,從歷史的DLL提取出本身想用的幾個接口,暴露出來。(例子來自網上查詢)app

#define SOFTWRAPPER_API extern "C" __declspec(dllexport) 

// 簡單的接口調用 SOFTWRAPPER_API int fnCppDll(int a, int b); //帶傳入數組: SOFTWRAPPER_API void testArray1(const int N, const int n[], int& Z); //帶傳出數組:C++不能直接傳出數組,只傳出數組指針, SOFTWRAPPER_API void testArray2(const int M, const int n[], int *N) ;

2. 對應的實現函數

// 1. 把數據底層封裝成一個全局函數,並導出
// 2. 編譯好以後拷貝到CSharp運行路徑學習

SOFTWRAPPER_API int fnCppDll(int a, int b)
{ return a+b; } SOFTWRAPPER_API void testArray1(const int N, const int n[], int& Z) { for (int i=0; i<N; i++) { Z+=n[i]; } } SOFTWRAPPER_API void testArray2(const int M, const int n[], int *N) { for (int i=0; i<M; i++) { N[i]=n[i]+10; } }

  編譯成DLL以後,程序會把這幾個接口暴露出去,可使用depends.exe查看導出狀況。網站

3. C#中對其調用spa

在C#中也創建一個類,專門用來管理這些接口

namespace CSharp
{
  // 3. 定義一個CSharp類,wrap c++的接口,方便CSharp使用 class CppDll { [DllImport("CppDll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int fnCppDll(int x, int y); [DllImport("CppDll.dll", EntryPoint = "#2", CallingConvention = CallingConvention.Cdecl)] public static extern double testArray(int N, int[] n, ref int Z); [DllImport("CppDll.dll", EntryPoint = "#3", CallingConvention = CallingConvention.Cdecl)] public static extern void testOutArray(int N, int[] n, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] Z); } }

這樣定義以後在其餘的C#程序中便可直接調用了。其中"CppDll.dll"是DLL文件名,保證在C#的輸出目錄下。聲明的函數名稱要麼和CPP中的一致,要麼不經過名字而經過EntryPoint = "#3"這種方式指定。確保編譯器能找到接口

此種方法也能夠實現類(C#中使用struct和IntPtr實現)的傳輸,可是須要在CSharp中從新編寫類,而且變量的順序、內存對齊方式可能都須要本身操心。尤爲是咱們類不少、類之間有嵌套等狀況

MS網站有更詳細的解釋PInvoke和Marshal技術,能夠根據這兩個關鍵字在網絡上好好查找下。

!!如何跟蹤調試:在C#工程屬性的Debug下面,check上Enable unmanaged code debugging便可。

二)SWIG

之前使用GDAL的時候,瞭解到GDAL能夠在多中語言和多種平臺下訪問,底層使用C++編寫,感受很好。前幾天也順帶看了下http://www.swig.org/

他的包裝能夠把類、接口很好的封裝起來,可是使用SWIG封裝的時候難度很大,要學習不少東西,網上有人評價,感受是在學習一門新的語言,因此也沒有進行一步深刻下去。

三)D-BUS

本是LINUX等系統上的技術,「dbus的是一個低延遲,低開銷,高可用性的ipc機制。是desktop-bus的簡稱」,由於我封裝的DLL主要是數據服務,因此當時考慮這條技術路線,使用DBus提供服務器端的數據服務,而後C#作爲客戶端直接訪問服務獲取數據,感受也是一條很好的路線。可是,感受不是很正規,也放棄了。

關於SWIG和DBUS網上文檔比較多,感興趣的能夠看看,多一種思路說不定何時能夠用到:)

四)C++/CLI

在看Mashalling的時候,MS網站上處處都是託管代碼這樣的概念,因而深刻了下,看了這個視頻以後感受這個很不錯。

http://www.microsoft.com/uk/msdn/nuggets/nugget/184/Wrapping-Windows-APIs-with-CCLI.aspx

經過這視頻瞭解瞭如何封裝C++接口,同時也能看到高手是怎麼編程的,受益不淺

 託管代碼簡單來講,就是在C++的基礎上進行擴展,使得能夠調用.Net裏面的類庫等東西。既然他是C++,那麼他訪問C++的DLL或者其餘C++庫,確定是沒有問題的了。另外一方面,他支持.Net類庫,那麼就是說能夠直接調用.Net裏面的各類庫了,同時提供了C++類型和.Net類型之間的各類轉換。進一步,咱們C#工程使用託管代碼組成的DLL便能很方便的訪問Native代碼了。託管代碼模塊起到了橋樑的做用,鏈接了Native C++和CSharp

1. 新建CLR的類庫:New Project下面選擇VC++裏面的CLR,下面的Class Libary。建立好以後,確保General屬性頁的Common Language Runtime Support 屬性設置成了Common Language Runtime Support(/clr)

2. 編寫類

#pragma once

using namespace System;
using namespace System::Data;

namespace CppSoftBridge {
	public ref class DataManger
	{
	public :
		bool Open(String^ path);
		property bool IsOpened{
			bool get(){return m_isOpened;}
		};
		property array<String^>^ AllData{
			array<String^>^  get();
		};
        }
}

其中class前面的ref說明這個類是託管的,要在C#中調用。一樣調用.Net類庫的類也要有所區分,就是這裏的符號 ^,至關於一個託管類的引用。

cpp中的實現

using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;
using namespace GPTSoftBridge;

array<String^>^ DataManger::AllData::get()
{
	List<String^>^ list = gcnew List<String^>();
	// TODO, 這裏能夠訪問Native C++裏面的接口
	list->Add("1");
	list->Add("2");
	return list->ToArray();
}

bool DataManger::Open(String^ path)
{
        return true;  // 根據本身須要進行實現便可
}
  • 這裏須要注意的是,這部分使用了.Net的東西,可是 仍是C++的語法,
  • 好比導入庫不是import,仍是using語句。
  • 包的組織不是System.Collections.Generic而是System::Collections::Generic;
  • 申請對象託管代碼須要使用gcnew,而不是new。
  • 申請以後如何判斷是否爲空呢?使用 if(p == nullptr)。
  • pin_ptr關鍵字能把託管引用轉換爲原生指針。   如: pin_ptr<BYTE> pBytes = & byteArray[0];

其他的.Net的類庫就直接使用,C++的老代碼也照經常使用就行了。

 

3. C#中使用

C#使用託管代碼,和使用C#編寫的類庫方式同樣。直接添加工程引用之後就能夠直接使用了。

DataManger dm = new DataManger();
if (!dm.Open("D:\\datal"))
     return;
String[] wells = dm.AllData;

就這麼多了,經過上面的步驟,咱們能很方便、快捷的把原來的DLL封裝起來,供C#調用。之後再作項目,咱們能夠輕鬆的選擇C#,而且能夠同時使用原有的C++代碼了。  

相關文章
相關標籤/搜索