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 調用類
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],如下:
#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命令可以查看一個可執行程序依賴的共享庫,例如我們編寫的測試動態庫依賴下面這些庫: