Unity/C++混合編程全攻略!——基礎準備

在Unity遊戲的開發當中,我們的遊戲項目變得越來越複雜,以至於有些項目功能必須通過依賴庫來進行實現。

比如,我們在手游開發中用到的toLua、FMOD或者是其他的插件,都是通過調用Native dll來實現一些複雜的功能。

那麼我們應該如何使用C#來對C++進行調用呢。

了解C#的人都知道,C#是運行在CLR之上被託管的,而C++則並沒有被託管。

這時候我們就需要通過一種機制來對C#的對象進行封送。

.Net互操作的三種方式

目前.Net平台中託管環境調用非託管環境有三種方法:

  • P/Invoke
  • C++ Interop
  • COM Interop

這三種方法當中,C++ Interop是針對託管C++使用的方法(說實話C++/CLI感覺沒啥前途),COM Interop則是針對Window軟體開發而採用的方式。所以我們只剩下一種解決方案:也就是PInvoke來進行託管環境與非託管環境的互操作。

當然也可以使用C++/CLI作為中間層,但是這樣就沒太大必要了,而且Mono好像也不支持……

所以,接下來的一系列內容都將從PInvoke進行展開。

如何使用PInvoke是接下來內容的重點。

不過由於PInvoke本身內容也並不少,所以在這裡我也就簡單介紹一下其使用的方式,更詳細的內容可以去查看官方文檔,或者是下一個《精通.Net互操作》的pdf來閱讀就可以了。在以後的文章中可能還會介紹Swig工具的使用,來幫助大家更輕鬆地在Unity中進行C#與C++的混編。

現在我們只在Windows平台下簡單使用PInvoke可以更好開始進行學習。

現在我們建立一個簡單的項目,設計一個加法的介面,每調用一次則加一併顯示在界面上。

從C++中導出函數

首先我們建立一個簡單的Win32工程。

建立一個Testlib.h與Testlib.cpp

工程目錄

__declspec(dllexport)代表需要導出的函數,需要放在函數定義的前面。

extern 「C」表示以C語言方式進行導出

__stdcall表示以標準方式調用。由於定義了extern 「C」與__stdcall,編譯器會對函數名進行整理,在庫中會獨立對應一個標識符,C#也會根據相同的規則去尋找符合條件的函數以進行調用。

在非託管dll導出的時候往往會用到不同的調用方式,所以相同的在C#中也可以通過調整DllImport中的CallingConvention進行指定以保證找到相應的函數。

TestLib.h

cpp文件中同樣要如此定義

TestLib.cpp

因為是基礎教程,這裡也簡單介紹下生成吧,如果要包含外部靜態庫的話大家就自行百度谷歌吧,也是比較容易的,因為我們目前只涉及簡單的介面對接的入門就不進行贅述了。

右鍵點擊工程,選擇屬性,選擇生成動態庫dll

設置項目屬性

選擇平台類型,

選擇平台類型

我們右鍵工程進行生成,分別生成x86與x64的版本。

分別將生成的.dll與.pdb放入到對應的文件夾中。x86放入x86文件夾,x64放入x86_64文件夾下。

我在這裡將導出的dll與pdb都重命名成了CppInterface。

在C#中編寫介面

在C#中想要調用我們編寫的C++函數也很簡單:

在函數定義的時候要在前面加上定義extern,告訴編譯器該函數在外部定義,不需要函數體,而且需要定義為靜態函數,因為非託管函數在導出之後都可以進行直接調用無需實例化就可以進行使用了。最後就是要加上DllImport,Unity會首先搜索Plugin,如果在Windows平台會去搜索系統目錄,如果仍未找到就會拋出DllNotFound異常。

注意這裡的DllImport中的CppInterface是導入的dll名,並非類名。

在實際開發時往往會涉及到更多的細節,例如指定字符集、指定調用方式、指定調用入口等等,特別是對字元串、結構體等對象的封送也會有不同的選擇,這裡為了快速入門也就不再提及了。日常開發中也不需要特別進行記憶,可以在需要使用到複雜對象的時候再去查詢相關的規則。《精通.Net互操作》這樣的書也可以作為工具書來進行使用。

在C#中進行調用

就像普通調用C#函數一樣就行了,下面是我編寫的更新界面的代碼。

用於更新UI的MonoBehavior

CppInterface便是我們編寫的介面。

在Unity下調試Native Dll

我們剛剛知道我們將.pdb文件導入到了Unity下,那麼這個文件就是記錄著調試信息的文件。

首先我們打開Unity工程,右鍵工程屬性,選擇允許調試NativeDll。

最後就選擇選項,附加進程:

這個時候我們就可以將我們的調試附加到Unity的進程上了。

點擊附加之後我們就可以添加斷點了。

總結

如果只是簡單功能調用的話,大家應該已經能夠在Unity中簡單調用非託管代碼了。本文沒有涉及任何複雜封送操作與內存管理,純粹幫大家邁出第一步!

在網上也有其他關於C#調用非託管代碼的文章,相較我的文章也會詳細一些,涉及到了指針封送以及函數指針封送,大家有興趣也可以去讀一讀:

cnblogs.com/warensoft/a

不過我更推薦大家可以去下一本《精通.Net互操作》的pdf來讀一讀,時不時可以作為工具書用於參考,因為.Net互操作並非經常會使用到的編程技巧,所以也沒有必要對其進行死記硬背。

我在之後的文章中會談一談

  • C++介面生成工具Swig以及與Unity的對接
  • 跨平台編譯非託管庫並且Unity中如何更好地進行協作,
  • 以及一些混合編程開發模式的思考,為什麼要使用混合編程?混合編程在項目實踐中的經歷與分享。

實際項目當中絕對不推薦大家自己去手寫互操作介面,能用工具就用工具,工具不能用就自己寫生成工具,如果lua作為膠水語言無法自動生成棧操作代碼的話大家肯定也很崩潰吧!

在項目中慢慢根據需求來使用各種互操作的封送以及內存管理,對互操作的掌握程度也會很快提升,比死記硬背要更有效。

大家有興趣可以看看我的小站~ 原文地址:Unity/C++混合編程全攻略!--基礎準備 | Unity之路


推薦閱讀:

UWA 兩周年慶活動第一彈!四場技術直播領跑六月充電季!
基於 Unity 引擎的遊戲開發進階之 全局光照
在Unity中復刻《超級馬里奧》
用好Lua+Unity,讓性能飛起來——Lua與C#交互篇
Unity什麼時候應該手動進行視域Culling?

TAG:Unity游戏引擎 | CC | 游戏开发 |