Linux系统上java调用C++ so库文件
PART1:
java中使用jna替代jni调用c++/c生成的 dll/so库文件需要做的事项
1、引入JNA依赖或者直接下载JNAjar包
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.2.0</version>
</dependency>
2、编写Java 调用类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.tree.go.util; import com.sun.jna.Library; import com.sun.jna.Native; //继承Library,用于加载库文件 --Class mapping public interface CPPTest extends Library { // 加载libhello.so链接库 public static final String JNA_ImgProcess = "hello" ; public static final CPPTest instance = (CPPTest)Native.loadLibrary(CPPTest.JNA_ImgProcess,CPPTest. class ); // 此方法为链接库中的方法 function mapping void test(); int addTest( int a, int b); //调用,singleton public static void main(String[] args) { CPPTest instance =CPPTest.instance; instance.test(); int c =instance.addTest( 10 , 20 ); } } |
接下来的工作就是如何编写可供调用的Cpp文件,以及编译加载的问题了,查看part 2
PART2:
编写C++/C文件,编译
准备编写C++代码[T1.cpp],如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <iostream> using namespace std; extern "C" { //避免name mangling,编译后名称symbol破坏,导致无法找到函数,告诉编译器下面的代码块使用c编译器来编译 int addTest( int a, int b) { cout << "a+b" << a + b << endl; return a + b; } void test() { cout << "hello word from C++ ! " << endl; } } |
1、如何将C++文件编译为so文件?
这里需要区分编译c文件和c++文件使用的是不同的编译器,具体编译参数可以复用
1.1、编译c文件使用 gcc
示例:
使用命令:gcc -fPIC -shared -o libGoT.so T1.c
1.2、编译c++文件使用的是 g++
g++ -fPIC -shared -o libhello.so T1.cpp
编译完成后生成如下文件:
2、如何加载编译好的so文件?
配置so文件加载位置:
打开 vim /etc/profile
添加如下配置:
/home/data/libso是自定义目录,
export LD_LIBRARY_PATH=/home/data/libso
多个目录用:隔开,如下
export LD_LIBRARY_PATH=/home/data/libso:/usr/lib
这样就可在Java中调用使用C程序编写好的代码了
PART3:
注意事项:
1、C++编译后函数名称破环问题
名称一致性问题,java中调用的和cpp文件中定义的名称需要保持一致
解决:使用 extern "C"放到一句代码前,或者一段代码前 extern "C"{ your code}
2、 编译生成动态库名的问题
注意在编译的时候一点要在库民前面加上 lib+soname.so
否则JNA如法加载到库文件
示例:我们需要一个hello库需要这样编译,前面加上lib
g++ -fPIC -shared -o libhello.so T1.cpp
linux动态库的命名规则
动态链接库的名字形式为 libxxx.so,前缀是lib,后缀名为“.so”。
针对于实际库文件,每个共享库都有个特殊的名字“soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件(lib+soname+.so)。
显式调用C++动态库注意点
对C++来说,情况稍微复杂。显式加载一个C++动态库的困难一部分是因为C++的name mangling;另一部分是因为没有提供一个合适的API来装载类,在C++中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这不容易做到。
name mangling可以通过extern "C"解决。C++有个特定的关键字用来声明采用C binding的函数:extern "C" 。用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。因此,只有非成员函数才能被声明为extern "C",并且不能被重载。尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。
另外如何从C++动态库中获取类,附上几篇相关文章,但我并不建议这么做:
l 《LoadLibrary调用DLL中的Class》:http://www.cppblog.com/codejie/archive/2009/09/24/97141.html
l 《C++ dlopen mini HOWTO》:http://blog.csdn.net/denny_233/article/details/7255673
“显式”使用C++动态库中的Class是非常繁琐和危险的事情,因此能用“隐式”就不要用“显式”,能静态就不要用动态。
附件:Linux下库相关命令
g++(gcc)编译选项
-shared :指定生成动态链接库。
-static :指定生成静态链接库。
-fPIC :表示编译为位置独立的代码,用于编译共享库。目标文件需要创建成位置无关码,念上就是在可执行程序装载它们的时候,它们可以放在可执行程序的内存里的任何地方。
-L. :表示要连接的库所在的目录。
-l:指定链接时需要的动态库。编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a/.so来确定库的名称。
-Wall :生成所有警告信息。
-ggdb :此选项将尽可能的生成gdb 的可以使用的调试信息。
-g :编译器在编译的时候产生调试信息。
-c :只激活预处理、编译和汇编,也就是把程序做成目标文件(.o文件) 。
-Wl,options :把参数(options)传递给链接器ld 。如果options 中间有逗号,就将options分成多个选项,然后传递给链接程序。
nm命令
有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种:
一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
一种是库中定义的函数,用T表示,这是最常见的;
一种是所谓的弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
$nm libhello.h
ldd命令
ldd命令可以查看一个可执行程序依赖的共享库,例如我们编写的测试动态库依赖下面这些库:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通