目录

一.引言

二.CLion

1.inter.h

2.test.cpp

3.编译 .dylib / .so

4.可能遇到的坑

三.IDEA

1.添加 jna 依赖

2.添加 libtest.dylib 到项目

3.Java 项目测试

四.总结


一.引言

下文主要讲解通过 CLion 将 C++ V14 编码为 .dylib 或者 .so 文件并使用 Java 调用 C++ 库示例。

实现上述需求将用到如下组件,使用其他编译器的同学可能会和下述介绍方法有不同:

-CLion

基于 C++ 14标准生成 .dylib 文件或 .so 文件,前者应用于 MacOs 后者应用于 Linux

– IDEA

基于 Java 1.8 通过 jna 库实现 C++ 动态库的调用。

恰逢卡塔尔世界杯火热进行中,这里也希望总裁能够在这一届世界杯走的更远,取得更好的成绩!

二.CLion

1.inter.h

在 .h 头文件中声明三维坐标类 Point,其中包含三个字段 x、y、z 定义坐标点。

// inter.h#includestruct Point{float x, y, z;};Point add(Point p);

2.test.cpp

.cpp 源文件中主要包含两部分内容:

第一部分为 Point add 方法,该方法将 Point 的 x、y、z 坐标均增加 1

// test.cpp#include#include"inter.h"Point add(Point point) {point.x += 1;point.y += 1;point.z += 1;return point;}

第二部分为 __cplusplus,_cplusplus 翻译过来其实就是 C++,其常与 extern “C” 搭配使用,其目的是标记一部分代码并指示编译器,这部分代码按 C 语言的格式进行编译,而不是 C++ 的。这是使用 C 扩展是因为 C 的函数名不会变,如果使用 C++ 变量名会发生乱码现象,导致我们在 Java 调用时方法名乱码无法调用。

这里Jna_add 方法很简单,对原始 Point 执行 add +1 操作,最终通过 ans 返回。阅读代码也可以看到,由于 ans 的类型为 Points *,其中 * 号代表指针,所以最后返回的是第一个结构体的指针。如果返回的为一个数组,需要返回长度信息才能输出结果。

#ifdef __cplusplusextern "C" {#endif__declspec(dllexport) Point* Jna_add(Point point) {Point a = add(point);Point* ans = new Point[1];for (int i = 0; i < 1; ++i) {ans[i].x = a.x;ans[i].y = a.y;ans[i].z = a.z;}return ans;}#ifdef __cplusplus}#endif

3.编译 .dylib / .so

– .dylib For MacOs

dylibs 文件应用于 MacOs 环境下的 Java 调用:

执行 Build Project 方法后生成 libXXX.dylib 文件,这里 XXX 与 CMakeLists.txt 中配置相关。由于下面的 Java 测试在 Mac 本机测试,所以博主测试样例使用 .dylib 文件。

– .so For Linux

在项目目录下执行:

g++ test.cpp -fPIC -shared -o libadd.so

-fPIC表示生成位置无关代码

-shared表示生成一个动态链接库

动态编译库名称为libXXX.so,其中 XXX 标识动态库名称。

Tips:

Window 环境下为 .dll 文件,如果使用该类型文件大家可自行搜索一下。

4.可能遇到的坑

A.__declspec attributes are not enabled

未启用 __declspec 属性,解决方法也很简单,在 CMakeLists.txt 中添加如下配置重新 ReLoad 配置即可:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fms-extensions")

修改后报错异常消失:

B.ninja: no work to do.

该异常出现在 Build 项目生成 .dylib 文件时,如果要生成对应文件,需要在 CMakeLists.txt 中添加要编译的信息与 cpp 源文件:

add_library(test SHARED test.cpp)

添加后 Reload 配置即可。

三.IDEA

1.添加 jna 依赖

net.java.dev.jnajna5.3.1

该依赖主要用于 Java 调用 C++ 生成的动态库。

2.添加 libtest.dylib 到项目

因为是测试,这里直接放置到 Java 项目根目录下,除此之外,也可以将文件放置在src/main/resources/linux-x86-64 、/usr/lcoal 等多个目录位置,Natice.load 方法会自动寻址。

3.Java 项目测试

这里也分为两部分分解,第一部分为 Jna 结构体,主要实现 C++ Point 类在 Java 中的重新定义,并实现 UserValue 继承 Point 实现静态类,其中元素类型 x、y、z 与之前的 float 对应。

– 通过 JnaLibrary 实现静态库的引入

– Jna_add 定义方法使用方式

// javapublic interface JnaLibrary extends Library {JnaLibrary INSTANCE = Native.load("test", JnaLibrary.class); // 引入 C++ 动态库Point Jna_add(Point.ByValue point); // 定义使用方式@Structure.FieldOrder({"x", "y", "z"}) // 构建结构体class Point extends Structure {public Point() {}public static class UserValue extends Point implements Structure.ByValue {public UserValue(float x, float y, float z) {super(x, y, z);}}public Point(float x, float y, float z) {this.x = x;this.y = y;this.z = z;}public float x;public float y;public float z;}}

第二部分为 main 主函数:

首先初始化一个静态类,随后调用 INSTANCE.Jna_add 方法并打印:

public static void main(String[] args) {JnaLibrary.Point.UserValue startPoint = new JnaLibrary.Point.UserValue(1, 2, 3);JnaLibrary.Point a = JnaLibrary.INSTANCE.Jna_add(startPoint);System.out.println(a.x);System.out.println(a.y);System.out.println(a.z);}

Tips:

这里注意方法名要匹配,jna_add、Jna_Add 等有大小写差异的都会异常报错。

运行后得到下述结果代表调用成功:

四.总结

上述方法实现了在 Java 中调用 C++ 库的简易方法,后续更多更复杂的操作还带进一步尝试。

参考链接:使用Java调取C++动态库。