JNA 调用自己的 SO 库:从编译到 Android 集成全记录

之前有过用 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 风格的函数符号。