构建 native 库的意义
- 核心算法保密(例如随机算法,寻路算法等)
- 效率考量(c++ 库效率比 C#要高一个数量级)
- 跨端需求(客户端,服务端)
目标
- 可以被用作 Unity Plugin,供 Unity Editor(Windows + Mac )使用
- 也可以编译为库文件,供移动端使用(Android + iOS)
准备工作
- 本文以 Mac 系统为例
- 安装 Homebrew
- 安装 CMake,一个跨平台的安装和编译工具,可以用简单的语句来描述所有平台的安装或编译过程
- JDK SDK NDK 这些可以使用 Unity 安装包内附带的
- XCode
- Visual Studio
native 插件的基本要素
1. 全局头文件 Define.h,定义 API,为了兼顾做 Unity-Plugin 和普通 dll 库,这里做一个宏定义判断(标准写法,全网通用)
#pragma once
// Unity native plugin API
// Compatible with C99
#if defined(__CYGWIN32__)
#define API __declspec(dllexport) __stdcall
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY)
#define API __declspec(dllexport) __stdcall
#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__)
#define API
#else
#define API
#endif
2.具体暴露给 Unity 的方法如何定义?例如 Encrypt.h,标准写法如下
#include "Define.h"
#ifdef __cplusplus
extern "C"
{
#endif
//Encrypt
void API EncodeNoGC(char* params, int paramsLength);
//Decrypt
void API DecodeNoGC(char* params, int paramsLength);
//The key
char KEY[] = "abcdefg123456";
#ifdef __cplusplus
}
#endif
这样的代码到底是什么意思呢?首先,__cplusplus 是 cpp 中的自定义宏,那么定义了这个宏的话表示这是一段 cpp 的代码,也就是说,上面的代码的含义是:如果这是一段 cpp 的代码,那么加入 extern "C"{和}处理其中的代码。
要明白为何使用 extern "C",还得从 cpp 中对函数的重载处理开始说起。在 c++ 中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在 C 中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++ 和 C 对产生的函数名字的处理是不一样的.
3.接下来具体逻辑怎么写?例如 Encrypt.cpp,标准写法如下
#include "Encrypt.h"
#ifdef __cplusplus
extern "C"
{
#endif
void API EncodeNoGC(char* params, int paramsLength)
{
//加密逻辑
}
void API DecodeNoGC(char* params, int paramsLength)
{
//解密逻辑
}
#ifdef __cplusplus
}
#endif
4.这样一个最基本的 native 就写完里,下一步是编译,我们使用 cmake,这里不做 cmake 科普,一个 CMakeLists.txt 如下
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
PROJECT(kernal)
AUX_SOURCE_DIRECTORY(src/. SRC_LIST)
FILE(GLOB_RECURSE HEADER_LIST src/*.h )
source_group("Header Files" FILES ${HEADER_LIST})
if ( WIN32 AND NOT CYGWIN AND NOT ( CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" ) )
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT" CACHE STRING "")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd" CACHE STRING "")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT" CACHE STRING "")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd" CACHE STRING "")
set(CompilerFlags
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
endif ()
if (APPLE)
if (IOS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode")
ADD_Library(kernal ${HEADER_LIST} ${SRC_LIST} )
set_xcode_property (kernal IPHONEOS_DEPLOYMENT_TARGET "7.0" "all")
else ()
ADD_Library(kernal MODULE ${HEADER_LIST} ${SRC_LIST} )
set_target_properties(kernal PROPERTIES BUNDLE TRUE)
endif ()
elseif (ANDROID)
ADD_Library(kernal SHARED ${HEADER_LIST} ${SRC_LIST} )
else ()
ADD_Library(kernal MODULE ${HEADER_LIST} ${SRC_LIST} )
set_target_properties(kernal PROPERTIES BUNDLE TRUE)
endif ()
这个 cmake 是包含各个平台的编译的,其中特别的
- Apple 平台下,Mac 出.boudle 库
- Apple 平台下,iOS 出.a 静态库
- Android 是要出 SHARED 包,也就是.so,静态库是不能用的
- Windows 部分没什么特别(个别代码摘自 xLua 的编译文件)
5.有了 CMakeLists.txt,各个平台执行 shell 或者 bat 包即可,仅举一个例子 Android
if [ -n "$ANDROID_NDK" ]; then
export NDK=${ANDROID_NDK}
elif [ -n "$ANDROID_NDK_HOME" ]; then
export NDK=${ANDROID_NDK_HOME}
elif [ -n "$ANDROID_NDK_HOME" ]; then
export NDK=${ANDROID_NDK_HOME}
else
export NDK=/Applications/Unity/Hub/Editor/2019.4.0f1/PlaybackEngines/AndroidPlayer/NDK
fi
if [ ! -d "$NDK" ]; then
echo "Please set ANDROID_NDK environment to the root of NDK."
exit 1
fi
function build() {
API=$1
ABI=$2
TOOLCHAIN_ANME=$3
BUILD_PATH=build_android_${ABI}
cmake -H. -B${BUILD_PATH} -DANDROID_ABI=${ABI} -DCMAKE_TOOLCHAIN_FILE=${NDK}/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=${API} -DANDROID_TOOLCHAIN=clang -DANDROID_TOOLCHAIN_NAME=${TOOLCHAIN_ANME}
cmake --build ${BUILD_PATH} --config Release
cp ${BUILD_PATH}/libkernal.so output/android/libs/${ABI}/libkernal.so
# cp ${BUILD_PATH}/libkernal.so ../unity/Assets/Plugins/kernal/Android/libs/${ABI}/libkernal.so
}
build android-16 armeabi-v7a arm-linux-androideabi-4.9
build android-16 arm64-v8a arm-linux-androideabi-clang
build android-16 x86 x86-4.9
最关键的先要指定 NDK 路径,然后就是按部就班的执行 build 了,所有的脚本执行完,得到如下 output 结果
接下来就是原封不动的扔到 Unity 的 Plugins 里去
6.在 Unity 中如何调用呢,废话不多说,直接上代码 Encrypt.cs
public class Encrypt
{
#if UNITY_IOS && !UNITY_EDITOR
const string ENCRYPT_DLL = "__Internal";
#else
const string ENCRYPT_DLL = "kernal";
#endif
[DllImport(ENCRYPT_DLL, EntryPoint = "EncodeNoGC", CallingConvention = CallingConvention.Cdecl)]
public static extern void EncodeNoGC(byte[] aData, int aLength);
[DllImport(ENCRYPT_DLL, EntryPoint = "DecodeNoGC", CallingConvention = CallingConvention.Cdecl)]
public static extern void DecodeNoGC(byte[] aData, int aLength);
}
注意这里的 CallingConvention 必须是 CallingConvention.Cdecl,Standard 调用在移动端是不被支持的
Demo
上面的代码仅仅是核心代码,一些 include 没有放进来,完整 Demo 可以看如下示例
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于