由于一些历史遗留问题,我们项目还在使用一套C++实现的Box2D定点数的库,由于最近修改了视野算法所以需要重新打包安卓的【.so】文件,特此记录
1、关于NDK
在Android平台,C/C++需通过NDK编译成动态链接库.so文件,然后C#中通过[DllImport(“soname”)]声明后即可调用。(我们项目使用了lua作为胶水层进行调用 原理一样但是需要单独打一个lua的so文件 在安卓平台上)windows 平台运行dll即可
C#是Unity的官方推荐的开发语言。如果某些逻辑需要C++支持以提供高性能特性,或者需要C++去跟底层硬件或者操作系统级别的接口交互,那么就需要用C#去调用C++的接口。
这往往依赖动态链接。即用C++开发动态链接库,然后C#调用动态链接库里暴露的接口。
而在Android上开发动态链接库(.so文件)的方法,就是NDK开发。
NDK,全称Native Development Kit,是Android的一种开发工具包。
目前的Android开发,不再是纯粹的Java层开发,更多的会与C/C++结合,把一些重要的方和行为以及一些私密性质的东西放在C/C++中,通过NDK将其编译成.so动态库文件,放入工程中的libs目录。
2.版本
注意需要下载的版本。如果你的NDK同时也为Unity的IL2cpp服务,那就需要注意Unity对NDK的版本有硬性要求,比如2018的版本要求 NDK r16b的版本
下载地址
下载下来的压缩包,解压之后会得到如下目录:
最重要的是那个叫做 ndk-build.cmd 的脚本文件。它是一个基于make的构建脚本。查看它的源码你会发现它实际上执行的是:
为了能够在全局使用这个脚本,你最好将这个目录加入到环境变量中,也可以在这个目录打开cmd 看个人习惯
3.利用NDK构建.so
现在开始利用NDK构建.so文件,目的是在.so文件中暴露一个函数给C#调用。这个函数是自定义的加法函数,它会将两个整型相加,再返回结果。
class MyClass
{
public:
static float AddFloat(float a, float b)
{
return a + b;
}
};
extern "C"
{
float AddFloat(float a,float b)
{
return MyClass::AddFloat(a,b);
}
}
我们将在这个文件所在的目录下运行ndk-build.cmd,在此之前,我们还需要两个文件:Android.mk 和 Application.mk。这两个文件将包含 ndk-build 所需要的所有配置。两个文件的内容如下:
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := gh_so
LOCAL_SRC_FILES := \
cs_call_c.c \
cs_call_cpp.cpp \
cpp_call_cs.h \
cpp_call_cs.cpp \
java_call_cpp.cpp \
cpp_call_java.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_STL := c++_static
APP_CPPFLAGS := -frtti -std=c++11
APP_PLATFORM := android-19
APP_CFLAGS += -Wno-error=format-security
APP_BUILD_SCRIPT := Android.mk
APP_ABI := armeabi-v7a x86
Android.mk 为 ndk-build描述了构建所需的源代码,链接库等信息。
变量 | 描述 |
---|---|
LOCAL_PATH | 这个变量描述了构建所需的源代码在当前工程中的相对位置。如果你是使用Android Studio进行开发,那源代码一般位于工程的 app/src/main/cpp 目录下。$(call my-dir) 返回Android.mk所在的目录。 |
CLEAR_VARS | 将一个特殊的Makefile文件包含进来。CLEAR_VARS定义了这个文件的位置。这个Makefile文件会清除所有之前定义过的Android.mk变量,一般都以LOCAL_开头 |
LOCAL_MODULE | ndk-build构建出来的库的名称,如果设定的名称没有以lib开头,则ndk-build会自动为你加这个前缀。例如,上面定义的名称将构建出来一个叫gh.so的库 |
LOCAL_C_INCLUDES | 当你的代码中include一些文件不在默认的搜索路径时,你可以使用这个变量去指定。一般来说,标准c头文件,和一些常见的Android头文件,都是在默认的搜索路径中的,他们都可以在你下载的NDK工具包中找得到 |
LOCAL_SRC_FILES | 需要构建的源文件(不包含头文件)都需要放到这里面 |
LOCAL_CFLAGS | 这个变量定义一些ndk-build的编译选项,一般来说,不需要额外的设置 |
BUILD_SHARED_LIBRARY | 将一个特殊的构建文件包含进来。BUILD_SHARED_LIBRARY定义了这个文件的位置。该文件会收集你在定义的所有上述LOCAL_开头的变量所包含的信息,然后确定如何从源文件中构建动态链接库。这个构建脚本要求你至少定义了LOCAL_MODULE和LOCAL_SRC_FILES两个变量。 |
Application.mk 为ndk-build描述了项目的构建性质。
|APP_STL | 需要使用哪些C++标准库 |
|APP_CPPFLAGS | 项目中,C++部分使用的编译选项 |
|APP_PLATFORM | 构建的Android API级别,对应于应用程序的min SDK Version |
|APP_CFLAGS | 项目中,C/C++部分使用的编译选项 |
|APP_BUILD_SCRIPT | Android.mk 文件所在的位置 |
|APP_ABI | 需要为哪些abi机器构建共享库 |
然后我们在终端输入以下命令:
ndk-build.cmd NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk
你会得到一个libs目录和一个obj中间文件暂存目录。libs目录里面就是对应你所设置的abi平台对应的两种架构的so文件
将so拷贝到Unity工程中的Assets/Plugins/Android/libs目录中
将我们打包出来的.so文件放到上述的目录下。因为不同abi的共享库名称都一样,为避免冲突,我们拷贝它的任意父目录即可。无论.so存放到哪儿,Unity都会识别出这个共享库属于哪个abi平台的。项目目录结构如下
4.通过DllImport调用
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
public class CsCallCCPP_DllImport_SO : MonoBehaviour
{
public Text txt;
public Button btn1;
public Button btn2;
void Start()
{
btn1.onClick.AddListener(() =>
{
//C#调用C
txt.text = "C#调用C接口: AddInt(2, 6) = " + AddInt(2, 6);
});
btn2.onClick.AddListener(() =>
{
//C#调用C++
txt.text = "C#调用C++接口: AddFloat(2.7, 4.2) = " + AddFloat(2.7f, 4.2f);
});
}
//C接口
[DllImport("gh_so")]
public static extern int AddInt(int a, int b);
//C++接口
[DllImport("gh_so")]
public static extern float AddFloat(float a, float b);
}
5.打包调用