一、什么情況下需要使用C++
1.大量的復雜運算,C++比C#效率高。
2.大多數(shù)語言都有調用C++ DLL的途徑,若項目中某個模塊客戶端和服務器都需要使用,可以考慮用C++實現(xiàn)該模塊,這樣客戶端和服務器就不需要重復編寫該模塊,只需寫一些膠水代碼即可。
二、基本概念
1.托管(Managed)和非托管(Unmanaged):.Net的運行環(huán)境是CLR(Common Language Runtime),運行在CLR上的代碼成為托管代碼(Managed Code),CLR提供了自動的垃圾回收機制(GC)。而C++是編譯后直接由操作系統(tǒng)執(zhí)行的代碼,不運行在CLR上,所以C++屬于非托管代碼(Unmanaged Code)。(注:另有托管C++,本質上也屬于.Net范疇,不在討論范圍內)
2.P/Invoke:P/Invoke(Platform Invoke,平臺調用)使得我們可以在托管代碼中調用非托管函數(shù),Unity與C++的交互都是通過P/Invoke實現(xiàn)。
三、Demo
1.創(chuàng)建C++ DLL
在VS中新建C++項目,這里理應選擇DLL,但是建議先選成控制臺,等我們編寫的函數(shù)先在控制臺調試沒問題后可以在項目屬性中改為DLL。


2.新建一個類Bridge.h和Bridge.cpp

//Bridge.h
#ifdef WIN32
#ifdef UNITY_CPP_INTEROP_DLL_BRIDGE
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllexport)
#else
#define UNITY_CPP_INTEROP_DLL_BRIDGE __declspec(dllimport)
#endif
#else
// Linux
#define UNITY_CPP_INTEROP_DLL_BRIDGE
#endif
extern "C"
{
UNITY_CPP_INTEROP_DLL_BRIDGE int Internal_Add(int a, int b);
}
//Bridge.cpp
#include "Bridge.h"
extern "C"
{
int Internal_Add(int a, int b)
{
return a + b;
}
}
3.拷貝DLL
右鍵項目生成DLL后,將DLL拷貝到Unity項目中。

4.在C#中調用DLL
在Unity中新建腳本填入如下內容。
// Use this for initialization
void Start()
{
int a = 5, b = 6;
Debug.LogError(string.Format("Internal_Add(): {0} + {1} = {2}", a, b, Internal_Add(a, b)));
Debug.LogError(string.Format("Add(): {0} + {1} = {2}", a, b, Add(a, b)));
}
[DllImport("UnityCppInterop")]
private static extern int Internal_Add(int a, int b);
[DllImport("UnityCppInterop", EntryPoint = "Internal_Add")]
private static extern int Add(int a, int b);
其中由兩個extern修飾的函數(shù)與C++中Internal_Add()函數(shù)對應,二者的區(qū)別在于是否指定了EntryPoint(入口),EntryPoint參數(shù)指明了從UnityCppInterop.dll中調用的函數(shù)名,如果未指定,則會調用與C#中函數(shù)名相同的C++函數(shù)。本例中,二者調用的是C++中的同一個函數(shù),輸出如下:

如果將Add()的EntryPoint刪除,會報EntryPointNotFoundException異常:

四、Android Studio中編譯so文件
上面只是介紹了在Windows平臺Unity與C++交互的過程,但發(fā)布到Android平臺后DLL是無法使用的,我們需要將C++源碼編譯成Android平臺可用的庫文件xxx.so。
1.新建Android工程
勾選Include C++ support,一路下一步即可。

創(chuàng)建好后檢查工程屬性中是否指定了ndk路徑:

修改app的build.gradle內容(供參考)如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.3"
defaultConfig {
applicationId "com.zqj.unitycppinterop"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk{
moduleName "native-lib"
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:26.+'
}
編譯so可以使用CMake或Android.mk,本文介紹CMake,如果沒有安裝,可在Android Studio的SDK Tools中下載。將之前編寫的C++文件拷貝到cpp目錄下,最好將頭文件和源文件分類,native-lib.cpp是新建工程時自動創(chuàng)建的,可以刪掉:

CMakeList.txt
cmake_minimum_required(VERSION 3.4.1)
#設置頭文件目錄
set(INCLUDE_DIR
"src/main/cpp/include/"
)
include_directories(${INCLUDE_DIR})
#需要編譯的源文件
file(GLOB_RECURSE SRC_FILE
"src/main/cpp/src/*.cpp"
)
#################################
add_library( # 最后生成的庫名稱
UnityCppInterop
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
# Associated headers in the same location as their source
# file are automatically included.
src/main/cpp/native-lib.cpp
${SRC_FILE} )
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
Make Project成功后,生成的libUnityCppInterop.so位于app/build/intermediates/cmake/debug中,把armeabi-v7a下的so文件拷貝到Unity工程Plugins/Android/目錄下。這里的so文件名比DLL多了lib前綴,不需要修改,Unity會自動識別。
總結
本文主要介紹Unity與C++交互的基礎知識,Demo只演示了最基本類型int型數(shù)據(jù)的交互,而實際項目中肯定會涉及到string, struct, class, 數(shù)組等復雜類型數(shù)據(jù)的傳遞,這其中有很多需要注意的地方,下一篇會針對這些進行介紹。