C#寫界面比較方便,而C++則擅長寫算法,所以將兩者結合起來將會加快程序的開發速度,並保證程序的質量。但C#與C++的混合編程有很多細節問題需要注意,下面簡要列舉一些並指出相應的解決辦法。
1. 將本機C++代碼(指非托管C++)編譯成一個dll,供C#調用,調用方法為 [DllImport(×××.dll)] 。但是這里只能從 DLL 導出函數,不能導出類(還沒有測試能否導出變量)。不能導出類是因為本機C++是非托管的,與C#的語言方式不兼容。也就是說,不能將此類dll作為引用 添加到C#工程中,IDE會提示不是一個程序集。
2. 利用CLR C++(指托管C++)編寫輸出類庫,供C#使用,由於CLR C++和C#都符合CLS規范,所以兩者可以無縫集成,在一個解決方案里包含這兩種語言的項目。生成的DLL可以導出類。但是CLR C++與傳統C++有很大的區別,可以認為是另一種不同的語言,學習它也要話費很大的精力,所以這種方法也有些麻煩。CLR C++不兼容本機C++的很多內容,但可以利用指針來操作。
3. 利用CLR C++把本機C++代碼包裝起來,做一個wrapper。這種方法比較好,而且在設計模式里還有一個專門的名稱。首先創建一個C#項目寫界面,然后添加一 個CLR C++類庫項目和一個本機C++ DLL項目。本機C++ DLL項目里面是算法代碼,可以導出類;在CLR C++類庫項目里寫一個類,私有成員為本機C++ 類的指針(不能用類的實例,因為CLS不支持混合類型),公共成員為本機C++ DLL類中的相應功能。C#調用CLR C++類,CLR C++類再調用本機C++ 類,表示如下:
| Native C++ ==>> Managed C++ Wrapper ==>> C# GUI |
如果按照上面的方法做會出現一些問題。比如本機C++文件DLLClass.h:
| // 此類是從DllClass.dll 導出的 classDLLCLASS_APICDllClass { public: CDllClass(void); // TODO: 在此添加您的方法。 }; |
托管C++文件AlgoCLR.h:
| namespaceAlgoCLR { publicrefclassClass1 { public: Class1(); ~Class1(); !Class1(); voidFunction(); private: void *pcls; }; } |
C#文件program.cs:
| Class1cls = newClass1(); cls.Function(); |
全部編譯成功后開始調試,調試器會停在 Class1cls = newClass1() 處,提示出現FileNotFoundException異常:
| 未處理 System.IO.FileNotFoundException Message="找不到指定的模塊。 (異常來自 HRESULT:0x8007007E)" Source="======" StackTrace: 在 ======.Program.Main(String[] args) 在 System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) 在 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) 在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 在 System.Threading.ThreadHelper.ThreadStart_Context(Object state) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 在 System.Threading.ThreadHelper.ThreadStart() InnerException: |
去掉Class1cls = newClass1() 這一行則不會出現問題。這是因為程序需要本機C++的DLL,而它沒有找到。在TargetDir(即××\bin\Debug目錄)里沒有本機C++的 DLL,但是有CLR C++的DLL,所以我們只需要將本機C++的DLL復制到該目錄中即可。在C#項目的“屬性”->“生成事件”的“生成后事件命令行”中輸入
copy $(SolutionDir)Debug\DllClass.dll $(TargetDir)
DllClass.dll為本機C++代碼生成的DLL。然后編譯運行,就可以看到正確的結果。附生成事件語法:
-
命令行編輯框
-
包含要為預先生成或后期生成運行的事件。
| 注意 |
| 在運行 .bat 文件的所有后期生成命令之前添加一個 call 語句。例如,call C:\MyFile.bat 或 call C:\MyFile.bat call C:\MyFile2.bat。 |
-
宏
-
展開編輯框,顯示要插入命令行編輯框中的宏列表。
-
宏表
-
列出可用的宏及其值。有關每個宏的說明,請參見下面的“宏”。一次只能選擇將一個宏插入命令行編輯框中。
-
插入
-
將在宏表中選擇的宏插入命令行編輯框中。
宏
可以使用以下任意宏來指定文件位置,或在存在多重選擇的情況下獲取輸入文件的實際名稱。這些宏不區分大小寫。
| 宏 |
說明 |
| $(ConfigurationName) |
當前項目配置的名稱(例如,“Debug|Any CPU”)。 |
| $(OutDir) |
輸出文件目錄的路徑,相對於項目目錄。這解析為“輸出目錄”屬性的值。它包括尾部的反斜杠“\”。 |
| $(DevEnvDir) |
Visual Studio 2005 的安裝目錄(定義為驅動器 + 路徑);包括尾部的反斜杠“\”。 |
| $(PlatformName) |
當前目標平台的名稱。例如“AnyCPU”。 |
| $(ProjectDir) |
項目的目錄(定義為驅動器 + 路徑);包括尾部的反斜杠“\”。 |
| $(ProjectPath) |
項目的絕對路徑名(定義為驅動器 + 路徑 + 基本名稱 + 文件擴展名)。 |
| $(ProjectName) |
項目的基本名稱。 |
| $(ProjectFileName) |
項目的文件名(定義為基本名稱 + 文件擴展名)。 |
| $(ProjectExt) |
項目的文件擴展名。它在文件擴展名的前面包括“.”。 |
| $(SolutionDir) |
解決方案的目錄(定義為驅動器 + 路徑);包括尾部的反斜杠“\”。 |
| $(SolutionPath) |
解決方案的絕對路徑名(定義為驅動器 + 路徑 + 基本名稱 + 文件擴展名)。 |
| $(SolutionName) |
解決方案的基本名稱。 |
| $(SolutionFileName) |
解決方案的文件名(定義為基本名稱 + 文件擴展名)。 |
| $(SolutionExt) |
解決方案的文件擴展名。它在文件擴展名的前面包括“.”。 |
| $(TargetDir) |
生成的主輸出文件的目錄(定義為驅動器 + 路徑)。它包括尾部的反斜杠“\”。 |
| $(TargetPath) |
生成的主輸出文件的絕對路徑名(定義為驅動器 + 路徑 + 基本名稱 + 文件擴展名)。 |
| $(TargetName) |
生成的主輸出文件的基本名稱。 |
| $(TargetFileName) |
生成的主輸出文件的文件名(定義為基本名稱 + 文件擴展名)。 |
| $(TargetExt) |
生成的主輸出文件的文件擴展名。它在文件擴展名的前面包括“.”。 |