---
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 專案

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 視窗看到結果:

### 達成串接。
<hr/>
:::warning
題外話:
`in` 關鍵字比較特別,它是傳址進去,因此:
| C# | C++ |
| -------- | -------- |
| in float, in int, ... | float \*, int \*, ... |
| in float \*, in int \*, ... | float \*\*, int \*\*, ... |
有點不符直覺,它跟 `out` 的對應是一樣的,如果想使用,C++ 那端一樣要再多加個星號,所以我覺得 `in` 這個關鍵字在本文主題中不要使用為佳。
:::