--- tags: 聯詠案 --- # 在 C# 中,如何將 Native (C++) DLL 串接 :::info 本文是以 Unity 為 C# 範例。 ::: ### 將 Unity 專案設定為允許 `unsafe` 程式碼 1. 新建/開啟 一個 Unity 專案 2. 玩家設定/播放器/其它設定/ -> `允許'不安全'程式碼` ### 建立一個 GameObject,這裡以 `Empty GameObject` 為例 1. 建立一個 Empty GameObject 2. 附加腳本,這裡取名為 `linkCppCode.cs` ### `linkCppCode.cs` ```csharp= using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; // 為了使用 DllImport using UnityEngine; public class linkCppCode : MonoBehaviour { void Start() { Call_C_Sharp_Function_Link_Cpp_Function(777.0f); // 呼叫,傳入 777,預期產生輸出: (777, 778, 779) } void Update() { } unsafe void Call_C_Sharp_Function_Link_Cpp_Function(float num) // why `unsafe`? 因為此函數中使用到指標 { float* position; // 為了傳給有 out 的參數,這裡必須是未初始化 C_Sharp_Function_Link_Cpp_Function(num, out position); // 連接 C++ 的 C# 端 function Debug.Log($"get position from c++: ({position[0]}, {position[1]}, {position[2]})"); } [DllImport("cppCode.dll", EntryPoint = "cpp_function")] // 匯入 Native DLL: `cppCode.dll` 中的函數 `cpp_function` static extern unsafe void C_Sharp_Function_Link_Cpp_Function(float num, out float* position); // 此函數會串接到 cppCode.dll 中的 cpp_function,使用到指標,必須標為 `unsafe` } ``` 這樣就準備好 C# 端的 code 了,現在來寫 C++ 部分,並匯出為對應平台的 Dynamic Linking Library。稍後會以 Windows 的 `.dll` 為例。 ### Visual Studio 中建立 DLL 專案 Visual Studio 要製作 DLL 很方便,可以直接新建一個 DLL 專案,最後建置可以直接變成 `.dll` 檔案。 1. 新建 DLL 專案 ![](https://i.imgur.com/eCN3ihP.png) 2. 預設會有 pch.h / pch.cpp / dllmain.cpp / framework.h 等,先不理它們,直接在專案加入兩個新項目,這邊以 cppCode.cpp 與 cppCode.h 為例 ### `cppCode.h` ```csharp= #pragma once #ifdef CPPCODE_EXPORTS #define CPPCODE_API __declspec(dllexport) #else #define CPPCODE_API __declspec(dllimport) #endif extern "C" CPPCODE_API void cpp_function(float num, float **positionPtr); // 參數必須對應,詳見以下說明。 ``` 其中 CPPCODE_API, CPPCODE_EXPORTS 這兩個 macro,視你的檔案名稱而定。假如你的檔案叫做 heheSmile.cpp 與 heheSmile.h,那 macro 叫做 HEHESMILE_API 與 HEHESMILE_EXPORTS,以此類推。 * 參數對應簡易規則: * 僅傳入,**不修改** | C# | C++ | | -------- | -------- | | float, int, ... | float, int, ... | | float \*, int \*, ... | float \*, int \*, ... | * 會修改,當作**傳出資料**用的時候 | C# | C++ | | -------- | -------- | | out float, out int, ... | float \*, int \*, ... | | out float \*, out int \*, ... | float \*\*, int \*\*, ... | 稍微解釋,當 C# 端為 out 時,C# 會預設自己即將看到一個指標,並收進來後解開指標。舉個例子: (out float \*) 這個情況 C# 會預設是 float \*\* 傳入,並解開指標把資料傳進來,因此傳入後變成 float \* 型態。所以 (out float \*) 這個情況 C++ 那邊就要把參數寫成 float ** 才能順利接上,並且須自己知道這個參數必須傳出點什麼才可以,例如: ```csharp= some_func(float **test) { float *something = new float[87]; ...do_something... *test = something; // 這行很重要,體會一下,基本上傳入的 float ** 這個指標的指標就是用來修改該記憶體變數的值的。 } ``` ### `cppCode.cpp` ```csharp= #include "pch.h" // 固定引入,照抄即可 #include "cppCode.h" // 引入你的 header file void cpp_function(float num, float **positionPtr) { float *position = new float[3]; // 注意,必須是動態配記憶體,不然離開這個 scope 就無效了。 position[0] = num; // 這裡我設計成:傳入的 num 為第一個值,接著 +1、+2 為第二、三個值 position[1] = num + 1.0f; position[2] = num + 2.0f; *positionPtr = position; // 重要,把剛剛創的 position 丟給 positionPtr 指的指標,藉此傳出 position 的位址給 C# } ``` ### 建置專案、產生 DLL ok,到目前為止只差最後一步,選定 **32 bits** or **64 bits** 後,建置專案,成功後會在 **Debug** or **Release** 資料夾看到你的 `cppCode.dll`,把此 DLL 丟到 Unity 專案裡,例如 Assets 中,確保剛剛已經寫好 C# 腳本後,執行吧!應該會在 Debug 視窗看到結果: ![](https://i.imgur.com/x8FAVfu.png) ### 達成串接。 <hr/> :::warning 題外話: `in` 關鍵字比較特別,它是傳址進去,因此: | C# | C++ | | -------- | -------- | | in float, in int, ... | float \*, int \*, ... | | in float \*, in int \*, ... | float \*\*, int \*\*, ... | 有點不符直覺,它跟 `out` 的對應是一樣的,如果想使用,C++ 那端一樣要再多加個星號,所以我覺得 `in` 這個關鍵字在本文主題中不要使用為佳。 :::