之前有过用 JNI/NDK 方式获取系统页大小,需要编写 JNI 胶水代码和 CMake 配置。现在想尝试用 JNA 调用自己的 .so,对比两种方式的差异。
现在开始从零开始编译一个导出 C 接口的 .so,然后在 Android 应用中用 JNA 加载并调用。
先新建一个普通c++工程,目录结构如下:

pagesize.hpp内容如下:
cpp
#ifndef PAGESIZE_HPP
#define PAGESIZE_HPP
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
// 获取系统内存页大小(字节),返回 long,出错返回 -1
long get_page_size(void);
#ifdef __cplusplus
}
#endif
#endif // PAGESIZE_HPP
注意,以上函数声明需要加上extern "C", 防止c++编译器优化修改函数名,导致JNA找不到要映射到的函数。
pagesize.cpp内容如下:
cpp
#include "pagesize.hpp"
#ifdef _WIN32
#define _WINSOCKAPI_
#include <windows.h>
#else
#include <unistd.h>
#endif
long get_page_size(void) {
#ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return static_cast<long>(si.dwPageSize);
#else
long page_size = sysconf(_SC_PAGESIZE);
// 如果 sysconf 返回 -1,表示获取失败,直接返回 -1(调用方自行处理)
return page_size;
#endif
}
CmakeLists.txt内容如下:
bash
cmake_minimum_required(VERSION 3.10)
project(cppLibs LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
file(GLOB SRC_FILES src/*.cpp)
add_library(cppLibs SHARED ${SRC_FILES})
target_include_directories(cppLibs PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/output)
下面使用 NDK 的交叉编译工具链,生成 ARM 架构的 .so。
先在工程根目录执行:
mkdir build && cd build, 如下

再执行:
bash
cmake .. -G "MinGW Makefiles" -DCMAKE_TOOLCHAIN_FILE="C:/Users/86186/AppData/Local/Android/Sdk/ndk/28.2.13676358/build/cmake/android.toolchain.cmake" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-29
执行过程如下:

再执行mingw32-make, 如下:

ok. 编译成功,可以看到output目录生成的libcppLibs.so :

现在有了so包。 再新建一个安卓工程,把so包复制到这里:

再在build.gradle.kts中引入JNA的aar包:
implementation("net.java.dev.jna:jna:5.17.0@aar")
如下:

再编写java的JNA接口,这里我新建了一个interface:
java
package com.example.testjna2;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface MyLib extends Library {
MyLib INSTANCE = Native.load("cppLibs", MyLib.class);
long get_page_size(); // 函数签名须和so里的那个函数一致
}
注意,Native.load 传入 "cppLibs",JNA 会自动补全为 libcppLibs.so。
然后写代码调用看下结果:

ok. JNA 的优势在于调用端代码极简,但前提是 .so 必须导出 C 风格的函数符号。