为什么要用 C++,什么时候该用 C++,及如何学习 C++
C++意义以及优势
- C++ 是一门多范式的通用编程语言。多范式,是因为 C++ 支持面向过程编程,也支持面向对象编程,也支持泛型编程,新版本还可以说是支持了函数式编程。同时,上面这些不同的范式,都可以在同一项目中组合使用,这就大大增加了开发的灵活性。因此,C++ 适用的领域非常广泛,小到嵌入式,大到分布式服务器,到处可以见到 C++ 的身影
- C++ 需要的代码行数一般是 Python 的三倍左右,而性能则可以达到 Python 的十倍以上。
C++应用
- 大型桌面应用程序(如 Adobe Photoshop、Google Chrome 和 Microsoft Office)
- 大型网站后台(如 Google 的搜索引擎)
- 游戏(如 StarCraft)和游戏引擎(如 Unreal 和 Unity)
- 编译器(如 LLVM/Clang 和 GCC)
- 解释器(如 Java 虚拟机和 V8 JavaScript 引擎)
- 实时控制(如战斗机的飞行控制和火星车的自动驾驶系统)
- 视觉和智能引擎(如 OpenCV、TensorFlow)
- 数据库(如 Microsoft SQL Server、MySQL 和 MongoDB)
什么时候该用C++?
- 当你的软件属于运算密集或者内存密集型,你需要性能、且愿意为性能付出额外代价的时候,应该考虑用 C++,特别在你的代码需要部署在多台服务器或者移动设备的场合。反之,如果性能不会成为你开发的软件的瓶颈,那 C++ 可能就不是一个最合适的工具。
什么是JNI
JNI(Java Native Interface)
- Java原生接口,是Java和其他原生代码语言(例如 C 和 C++)通信的桥梁。
NDK(Native Development Kit)
- 原生开发工具集,是一套允许您使用原生代码语言(例如 C 和 C++)实现程序功能的工具集。
ABI(Application Binary Interface):
- 应用程序二进制接口,不同的CPU支持不同的指令集,CPU与指令集的每种组合都有其自己的应用二进制接口(或ABI),ABI可以非常精确地定义应用的机器代码在运行时如何与系统交互。
- 支持的ABI:armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips、mips64
CMake
- Android推荐使用的NDK构建工具,从AS 2.2版本之后开始支持(包含2.2版本)
环境搭建
在SDK Tools中安装以下组件:
- Cmake:NDK构建工具
- LLDB:NDK调试工具
- NDK:NDK开发工具集
- 最新版本的Android Studio没找到LLDB开启的位置
使用Android Studio创建C++项目
选项解释
- C++ Standard:C++标准,选择【Toolchain Default】会使用默认的CMake配置。
NDK项目创建完成之后,目录解释
先看build.gradle文件与普通的Android项目的区别
-
发现多了externalNativeBuild以及cmake的配置
-
主要定义了CMake的构建脚本CMakeLists.txt的路径
-
defaultConfig下多了一些配置
-
主要配置了Cmake的命令参数
CMake的构建脚本CMakeLists.txt
CMakeLists.txt是CMake的构建脚本
分析CMakeLists.txt文件里面代码的含义
# 设置Cmake的最小版本
cmake_minimum_required(VERSION 3.18.1)
# 编译library
add_library(
# 设置Library的名称.
mnn516
# 设置library模式
# SHARED模式会编译so文件,STATIC模式不会编译
SHARED
# 设置原生代码路径.
native-lib.cpp)
# 定位library
find_library(
# Sets the name of the path variable.
log-lib
# 将library路径存储为一个变量,可以在其他地方用这个变量引用NDK库
# 在这里设置变量名称
log)
# 关联library
target_link_libraries( # Specifies the target library.
mnn516
# Links the target library to the log library
# included in the NDK.
${log-lib})
原生代码native-lib.cpp
- Android提供了一个简单的JNI交互Demo,返回一个字符串给Java层,方法名是通过 Java_包名_类名_方法名 的方式命名的:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_mnn516_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
Java调用
package com.example.mnn516;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivityDemo extends AppCompatActivity {
/**
* 加载Library
*/
static {
System.loadLibrary("mnn516");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_demo);
initUI();
}
/**
* 声明一个公共的native方法以供cpp文件去实现
* @return
*/
public native String stringFromJNI();
/**
* Java调用公共的native方法以获得c++那边的返回值
*/
private void initUI() {
TextView tv = findViewById(R.id.mTv);
tv.setText(stringFromJNI());
}
}
生成so包
首先设置编译模式为编译完生成so包
然后build之后,就可以在下面图面的目录中找到对应架构的so包了
生成的so包如何在其他项目中使用?
在app下的src文件夹下的main文件夹下创建jniLibs即可
关于为什么要在指定目录下创建jniLibs这个文件夹名字的问题其实是有点意思的
这里有一些潜规则以及坑点或者说让人迷惑的东西
- 是因为如果你按照上面的目录结构去导入so包,那么不用任何修改,项目就可以加载到so包,否则你需要指定so包的加载路径才可以
- 如果不按照上面的路径去导入so包,那么项目就会报错so包找不到
- 如果你非要放在其他目录下,并且so包文件夹的名字也要自己起名字,那么就需要配置一下了
- 配置规则如下:
- 比如你修改为libs,那么就需要在build.gradle下重新指定一下
第二个坑点
so包加载好了,我们就需要调用so包里边的C++函数了
调用之前我们首先要确定几点
- 需要知道so包里边的函数的完整名字
- 例如:so包里的函数名字是这样的
Java_com_example_mnn516_MainActivityDemo_stringFromJNI
- 那么我们需要在我们的项目里创建包名为com.example.mnn516文件名字为MainActivityDemo的调用文件,然后这个文件中声明native方法,方法名为stringFromJNI
- 假设你不这么做,那么又会给你报错,该方法找不到。。。。。
- 所以,针对上面的so包中的方法名,我做了以下的操作,创建相同的包名、文件名、并且声明该方法
- 然后activity中调用。。。
- 成功~