CMake交叉编译及iOS与Android端进行调用

该笔记记录使用CMake在MacOS平台打包出iOS静态库与Android动态库,并分别在iOS端与Android端进行调用

编译iOS静态库

编译iOS静态库是参考CMake的这份文档

请注意这里使用的版本是cmake version 3.18.4,cmake对于iOS的打包是最近几个版本才支持的,之前需要自己手动去找iOS的toolchains

好了现在,让我们创建一个极其简单的Cpp程序,这个程序不引用其他的静态库以及动态库,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//hello.h头文件

#include<stdio.h>

int add(int a,int b);

//hello.cpp文件

#include "hello.h"

int add(int a, int b){
return a + b;
}

我们在这个两个文件的同级目录中创建一个CMakeList.txt,并编写CMake的指令,指令非常简单就是将上述程序打包成静态库

这里我是分别处理的,此时只为了打包iOS静态库,实际上你可以将所有指令写入CMakeList.txt中,指令的逻辑是分别打包iOS静态库以及Android动态库,稍后我会做一个汇总的CMakeList.txt方便复用

所以此时CMakeList.txt的内容为:

1
2
3
4
5
PROJECT(crossDemo C CXX)

ADD_LIBRARY(hello STATIC hello.cpp)

INSTALL(TARGETS hello DESTINATION lib)

创建在此目录中创建build目录,并在build目录中进行构筑,根据Cmake的官方文档我们只需要使用如下命令就可以进行构筑

1
cmake .. -GXcode -DCMAKE_SYSTEM_NAME=iOS

但此时构筑的静态库使用的ABI有问题,为了支持更多的架构通常我们会使用如下命令

1
2
3
4
5
6
7
cmake .. -GXcode \
-DCMAKE_SYSTEM_NAME=iOS \
"-DCMAKE_OSX_ARCHITECTURES=armv7;armv7s;arm64;i386;x86_64" \
-DCMAKE_OSX_DEPLOYMENT_TARGET=9.3 \
-DCMAKE_INSTALL_PREFIX=`pwd`/_install \
-DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \
-DCMAKE_IOS_INSTALL_COMBINED=YES

通过这个命令打包出来的静态库支持armv7;armv7s;arm64;i386;x86_64这几种架构的ABI

注意执行上述命令,你可能会遇到这个问题

1
iphoneos is not an iOS SDK

该问题可以通过如下命令来解决

1
sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

使用 GXcode构筑iOS库还需要一步安装步骤才可以得到我们需要的静态库,根据官方的文档你需要执行如下命令

1
cmake --build . --config Release --target install

如果你希望在iOS的模拟器中使用打出来的静态库你需要使用这个命令

1
cmake --build . --config Release --target install -- -sdk iphonesimulator

经过这个步骤,若没有其他错误,你就可以得到libhello.a这个静态库

我们可以使用命令行工具来查看这个库的支持平台以及一些具体信息

1
2
3
lipo -info libhello.a 

otool -l libcross.a

在Xcode中使用iOS静态库

我通过Xcode直接创建了一个基于Objective-C与UIKit的iOS工程

实际上在iOS中使用静态库非常容易,因为OC本身就支持与C/C++进行混编,你需要做的仅仅就是将刚刚的hello.h这个头文件加入工程,并将打包出来的libhello.a这个静态库拖到Xcode的工程中,拖入时会弹出对话框,你需要勾选

  • ☑️ Copy items if needed
  • ☑️ Create groups

接着设置工程的Targets-Build Phases-Linked Binary with Libraries中添加我们刚刚拖进来的静态库

最后就是在代码中调用这个hello的add函数了,我是直接在ViewController.m中引用了hello.h,并调用add方法,注意你需要将ViewController.m->ViewController.mm否则运行时会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "ViewController.h"
#import "hello.h"

@interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];

int a = add(1,2);
NSLog(@"a= %d",a);
}

@end

打包Android动态库

打包Android比较容易,你需要下载Android studio,并安装NDK组件

安装方式:Tools - SDK Manger - 找到SDK Tools这个子tag,选择NDK安装

接着我们回到之前的CMakeLists.txt,修改ADD_LIBRARY() 为动态库

最后创建build目录,进入到目录中执行如下命令进行构筑

1
2
3
4
5
6
cmake ../src \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=21 \
-DCMAKE_ANDROID_ARCH_ABI=x86_64 \
-DCMAKE_ANDROID_NDK=/path/to/android-ndk \
-DCMAKE_ANDROID_STL_TYPE=gnustl_static

这里你需要选择ABI,由于我是在模拟器中执行所以选择了x86_64,另外如果你的NDK版本较高可能设置DCMAKE_ANDROID_STL_TYPE=gnustl_static会报错,根据官方的问文档你可以对该STL的type为c++_shared

最后执行make命令,Android的动态库hello.so就打包好了

在Android Studio中使用动态库

这里似乎比较麻烦,我的理解是为了是Android调用动态库,你需要使用Java或者说jvm的JNI这个功能

JNI调用c++代码是反着的,就是你先定义好Java要调用的方法,声明即一个native方法,紧接着使用javah命令根据这个Java类生成一个C的头文件,你需要依据这个头文件编写对应的C++方法,简单来说就是用c++实现java定义的接口,而不是java直接调用c++方法,并且为了在工程中使这个c++代码执行,你需要将这个c++代码编译成动态库由Java来加载,在JVM中当native方法被调用时会来执行这个动态库的实现。

所以为了让Android调用我们打出来的so动态库,我们需要做一个套娃操作,即先用java定义native方法,并生成头文件,我们在实现这个方法时调用so动态库的方法,最后使用Android的Gradle使用CMake或者NDK,将c++代码与so链接打包

本文结束 感谢阅读
0%