UnityでC++連携をする(2020年度版)

# 概要
サーバー連携をする際に送る認証用データを作ったりしたくて、そういった箇所はリコンパイルが(比較的)難しいC++側で作り込みたいなと思ったのでそのやり方をまとめます。
まずはC++の簡単なファイルを作ってのUnity連携までを記載します。

なお、連携部分は以下を大いに参考にさせていただきました。

Unity iOS/Android共用プラグイン C++ライブラリ作成
https://qiita.com/satotin/items/05fad323de3101f775d5

## 動作環境
* Mac OSX (10.15.4)
* Xcode 11.4.1
* Android Studio 3.5
* Android NDK r17b
* Crypto++ 8.2.0
* Unity 2019.3.13f1

# Unityプロジェクトの作成
とりあえず、サクっとUnityプロジェクトを作成します。こちらは割愛。

## Assetsフォルダ直下に必要なフォルダを作成

以下になるように、Assetsフォルダ内にフォルダを作成していきます。

```
■ Assets
├ ■ Plugins: ネイティブ側の連携ソースを入れる
| ├ ■ iOS: iOSのビルド済のFrameworkを格納
| └ ■ Android: Androidのビルド済のsoファイルを格納
└ ■ Scripts: C#のスクリプトを格納
■ src: C++のビルドする環境などを格納
└ ■ CppBridge: C++のソースファイルを格納 (名前は何でもいいですが以下はこの前提で記載しています)
```
srcファイルは、Assetsの外側に配置します（そうしないと中のC++ファイルもビルド時に取り込まれちゃう）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/d39dc16b-a8b4-80c1-c8d1-044b454f409f.png)


# src 直下に Static Library を作成
とりあえずC++をいじるのにXcodeの環境を使っていきます。ということでStatic Libraryを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/436265f2-f9cb-66df-590d-7762f40aeed8.png)

LanguageはSwiftだとC++連携も面倒なのでObjective-cにします。
とはいってもObjective-c は特には使わないから何でもいいんですが。

## iOS側のビルド設定
Deployment Targetを `8.0` にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/b590f4c1-cb15-79af-4cab-93e153ddc103.png)

その後、Build Settingsより、以下を変更します。

* `Supported Platforms: iphoneos`
* `Build Active Architecture Only : Debug / Releaseともに YES` 
* `Valid Architectures: arm64 armv7 armv7s`

## Android側のビルド設定
src直下に以下をそれぞれ作成します。

```makefile:Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := CppBridge_shared
LOCAL_MODULE_FILENAME := libcppbridge
LOCAL_CFLAGS    := -Werror
CPP_FILES := $(shell find $(LOCAL_PATH)/CppBridge -name *.cpp)
CPP_FILES += $(shell find $(LOCAL_PATH)/CppBridge -name *.c)
LOCAL_SRC_FILES += $(CPP_FILES:$(LOCAL_PATH)/%=%)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/CppBridge
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)
```

```makefile:Application.mk
APP_STL := c++_static
APP_CPPFLAGS := -frtti -DCC_ENABLE_CHIPMUNK_INTEGRATION=1 -std=c++11 -fsigned-char -Wno-extern-c-compat
APP_LDFLAGS := -latomic

APP_OPTIM        := debug
APP_ABI          := armeabi-v7a arm64-v8a x86
APP_PLATFORM     := android-14
APP_BUILD_SCRIPT := Android.mk
```

## ビルドスクリプトの作成
Xcodeビルド時にiOS / Androidの両方ともビルドされるようにBuild Phases -> Run Scriptに処理を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/c656b2de-1f3e-e8ab-ed77-e0e588a627ed.png)

```sh:スクリプトの内容
# iOSの成果物を移動
mv -f $TARGET_BUILD_DIR/libCppBridge.a $SRCROOT/../Assets/Plugins/iOS

# Androidの実行プログラム作成＆移動
ANDROID_NDK_ROOT=<Android_NDKのパス>

$ANDROID_NDK_ROOT/ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=$SRCROOT/Application.mk $*
rm -rf $SRCROOT/../Assets/Plugins/Android/**
mkdir $SRCROOT/../Assets/Plugins/Android/libs
mv -f $SRCROOT/libs/** $SRCROOT/../Assets/Plugins/Android/libs/

rm -rf $SRCROOT/libs
rm -rf $SRCROOT/obj
```

## ビルド時の動作確認
ひとまず、簡単なC++のファイルを作ってビルドをしてみます。
https://qiita.com/satotin/items/05fad323de3101f775d5 に記載の内容をそのまま使わせていただきました。

```c++:NativeBridge.hpp
#ifndef NativeBridge_hpp
#define NativeBridge_hpp

extern "C" {
    int test();
}

#endif /* NativeBridge_hpp */
```

```c++:NativeBridge.cpp
#include "NativeBridge.hpp"
#include <stdio.h>

#ifdef __ANDROID__
#include <android/log.h>
#endif

int test(){
#ifdef __ANDROID__
    __android_log_write(ANDROID_LOG_DEBUG, "hello.cpp", "Hello,World!");
#else
    printf("Hello,World!\n");
#endif
    return 0;
}
```

XcodeでRunして、以下のように表示されてたら（多分）成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/d1ed79a9-aec9-00df-a96d-23248bf24d4d.png)

その際、Unityへの配置は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/fe8daeab-ded2-fe68-b41c-2719d9889ac3.png)

ちなみに、実行時にこんなのが出る場合、セキュリティでXcodeを許可するようにしましよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/913ac0a0-02a3-1b31-d603-1e13c0ba9da0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/69c2dde2-6ec6-f8bd-f347-f4485d2c5976.png)

# Unity側での動作確認
そんなわけで、いよいよUnityでの動作確認を行います。

その前に、配置された実行ファイルのプラットフォームが正しく設定されているかどうかを確認します。

## Android側

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/15175913-0182-ff6c-978c-b513f238b8dd.png)

いずれの実行ファイルも `Android` にだけチェックがついてたらOKです。

## iOS側

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/55ba586b-1ee9-1b5a-6f29-27580f327b70.png)

`iOS` にだけチェックがついてたらOKです。

## C#のソース作成
こちらも、 以下を大いに参考にさせてもらいました（というかほぼコピペさせてもらいました）
https://qiita.com/satotin/items/05fad323de3101f775d5

```c#:Scripts/PluginCall.cs
using UnityEngine;

public class PluginCall : MonoBehaviour {
    // Start is called before the first frame update
    void Start() {
        
    }

    // Update is called once per frame
    void Update() {
        
    }

    public void OnClickedButton() {
        Debug.Log("OnClickedButton");
        Plugins.NativeBridge.HelloTest();
    }
}
```

上記は、適当なボタンにアタッチしてOnClickedButtonが呼ばれるようにしておきます。

```c#:Plugins/NativeBridge.cs
using UnityEngine;
using System.Runtime.InteropServices;

namespace Plugins {
public class NativeBridge {
#if UNITY_IOS || UNITY_ANDROID
    #if UNITY_IOS 
        [DllImport ("__Internal")]
    #elif UNITY_ANDROID
        [DllImport ("CppBridge")]
    #endif
    private static extern int test();
#endif
    public static int HelloTest() {
#if UNITY_EDITOR
        Debug.Log("Call Native");
        return 1;
#else
            return test();
#endif
    }
}
}
```

一応はUnity Editorでも動かせるように適当な値を返すようにしてます。

## 実機ビルド
あとは、実機でビルドします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/50d95512-349d-8386-0ab1-95bb8434023b.png)

iOS側は以下のように表示されました。

```sh
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

Hello,World!
```

Android側のLogCatもこんな感じで表示されます。

```sh
05-10 20:46:00.171 20232 20261 D hello.cpp: Hello,World!
```