第一章、建立自己的C函式庫
程式在執行時,會自動去連結libc.so,這是基本的c函式庫,也就是compile時不用加上-lc,就會自動進行連結。共享函式庫和靜態函式庫的不同在於,不論多少程序呼叫共享函式庫,它只使用一份記憶體,因此非常節省記憶體空間;並且在修改函式庫後,使用該函式庫的程式並不用重新編譯,只需要編譯函式庫本身即可。共享函式庫的代價就是程式的複雜度提高,若二進位檔搬去別的電腦執行時找不到共享函式庫,則程式會無法執行。
以下是建立"自己"的共享函式庫的步驟。
建立hello.c和hello.h
#include
void helloworld() {
printf("hello world!\n");
}
gcc -fPIC -Wall -c hello.c //編譯時依照機器來產生hello.o,拿到別的機器上hello.o即無效
gcc -shared -o libhello.so hello.o //編譯成libhello.so,加入hello.o
建立caller.c
#include "hello.h"
#include
int main() {
printf("call hello.a\n");
helloworld();
return 1;
}
gcc caller.c -o caller -lhello -L. //編譯時指定連結libhello.so,並且指定目錄在.
接下來有兩種方法可以讓二進位執行檔找到libhello.so:
cp libhello.so /lib/ //直接copy檔案到預設library的目錄下,程式執行時會自動去該目錄找尋
LD_LIBRARY_PATH=`pwd` ./caller //在執行前設定LD_LIBRARY_PATH變數,程式執行時就會去找該目錄下的lib
完成自製的共享函式庫了。
補註:如果想要在一個lib中放入多個object檔案,只要在第三步時,將多個.o檔加在參數之後即可。
ex:gcc -shared -o libhello.so hello.o hello2.o hello3.o
第二章、使用Java Native Interface呼叫C程式
如何讓JAVA程式呼叫以前寫好的C程式?JNI可以在JAVA程式中呼叫不同語言寫成的程式。以下是以C語言為例:
建立JAVA程式,其中宣告native function的格式為:public native void functionname()
class HelloWorld {
public native void helloworld();
static {
System.loadLibrary("hello");
}
public static void main(String[] args) {
new HelloWorld().helloworld();
}
}
javac HelloWorld.java //編譯JAVA程式
javah -jni HelloWorld //產生對應C程式的標頭檔,內容中粗體為C的函式宣告:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: helloworld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_helloworld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
實作C函式的內容於HelloWorld.c,以下為其內容:
注意其中兩個參數JNIEnv *和jobject都是預設就會幫你傳的參數,故在java中呼叫native時並不用去理會這兩個參數。
#include
#include "HelloWorld.h"
#include
JNIEXPORT void JNICALL
Java_HelloWorld_helloworld(JNIEnv *env, jobject obj)
{
printf("Hello world!\n");
return;
}
gcc -fPIC HelloWorld.c -c -I/usr/local/j2sdk1.4.2_06/include -I/usr/local/j2sdk1.4.2_06/include/linux //編譯HelloWorld.c成Object file
gcc -shared -o libhello.so HelloWorld.o //產生hello library,命名為libhello.so
LD_LIBRARY_PATH=`pwd` java HelloWorld //執行java HelloWorld之前必需要設定LD_LIBRARY_PATH後才能讓JNI找到libhello.so
完成
第三章、JNI進階一-從Java中傳參數給C程式
只能呼叫C的程式並不能滿足我們的需求,當我們要傳入參數給C的函式時,我們可以傳入Java中定義的原生型態,並且在C函式中直接使用這些原生型態,以下是原生形態的對應列表:
Java Type
Native Type
Size
boolean
jboolean
8
byte
jbyte
8
char
jchar
16
short
jshort
16
int
jint
32
long
jlong
64
float
jfloat
32
double
jdouble
64
void
void
n/a
若要使用Object作為參數的話,參數就會是jobject,JNI定義了一些方法來幫助我們轉換jobject來讓C程式使用,這裡以String和Array兩種物件為例子。
建立JAVA程式,其中傳入參數為String,其餘和第二章中相同。
public native void helloworld(String param);
javac StringHW.java //編譯JAVA程式
javah -jni StringHW //產生對應C程式的標頭檔,內容中粗體為C的函式宣告
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class StringHW */
#ifndef _Included_StringHW
#define _Included_StringHW
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: StringHW
* Method: helloworld
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_StringHW_helloworld
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
開始實作C函式的內容。這裡使用JNIEnv中的函式來處理jstring
#include "StringHW.h"
#include
JNIEXPORT void JNICALL Java_StringHW_helloworld
(JNIEnv *env, jobject obj, jstring param)
{
int str_len;
char *buf;
//get string length
str_len = (*env)->GetStringLength(env, param);
//get UTF-8 encoding char array
buf = (*env)->GetStringUTFChars(env, param, 0);
printf("UTF param:%s[%d]\n",buf,str_len);
//after using jstring object you must release it
(*env)->ReleaseStringUTFChars(env, param, buf);
//get unicode encoding char array
buf = (*env)->GetStringChars(env, param, 0);
printf("Char param:%s[%d]\n",buf,str_len);
//release string
(*env)->ReleaseStringChars(env, param, buf);
}
其餘同第二章的步驟。
第四章、JNI進階二-由C函式中存取Java中的變數
要達到Java和C程式之間的互動,C程式必需也可以去修改Java物件中的成員變數。JNIEnv提供了一組函式讓C程式可以順利地取存Java中的變數。
Java定義的成員變數中,每個都有signature(簽章)。在native code中要存取成員變數時,必須要先得知變數的簽章。以下指令是用來取得變數簽章:javap -s -p classname。該指令會列出所有成員變數的簽章,其內容大約如下:
Compiled from "TMC_GUIWarrant.java"
public class TMC_GUI.TMC_GUIWarrant extends javax.swing.JFrame{
java.lang.String ExpDate_str;
Signature: Ljava/lang/String;
java.lang.String UpperWarrantBin_str;
Signature: Ljava/lang/String;
java.lang.String OriSignerCertPath_str;
Signature: Ljava/lang/String;
java.lang.String ProxySignerCertPath_str;
Signature: Ljava/lang/String;
.
.
.
static javax.swing.JLabel access$000(TMC_GUI.TMC_GUIWarrant);
Signature: (LTMC_GUI/TMC_GUIWarrant;)Ljavax/swing/JLabel;
static javax.swing.JLabel access$100(TMC_GUI.TMC_GUIWarrant);
Signature: (LTMC_GUI/TMC_GUIWarrant;)Ljavax/swing/JLabel;
static javax.swing.JLabel access$200(TMC_GUI.TMC_GUIWarrant);
Signature: (LTMC_GUI/TMC_GUIWarrant;)Ljavax/swing/JLabel;
static javax.swing.JLabel access$300(TMC_GUI.TMC_GUIWarrant);
Signature: (LTMC_GUI/TMC_GUIWarrant;)Ljavax/swing/JLabel;
static {};
Signature: ()V
}
第五章、FAQ[常見問題解答]
Q1:在JAVA程式執行時會出現java.lang.UnsatisfiedLinkError,是什麼問題?
A1:表示在System.loadLibrary時找不到要載入的動態函式庫,請檢查LD_LIBRARY_PATH環境變數是否已經設定,或是在執行java的命令列中加入-Djava.library.path=[PathToLibrary]參數,讓JVM知道去哪裡找這些函式庫。
後來我又發現一種問題會造成java.lang.UnsatisfiedLinkError,那是libXXX.so中如果有函式名稱無法連結的話,亦會造成java無法載入該libXXX.so。但是gcc在編譯library時不會顯示這些函式名稱無法連結的錯誤,例如像warning: implicit declaration of function xxx(在動態函式庫中,找不到函式名稱是很正常的事,因為函式可能在別的library中有定義),所以強烈建議在編譯時加上-Wall的參數,gcc才會顯示這些警告。
Q2:將java的類別放到package中後,可以載入動態函式庫,但是沒辦法使用native function了 ?
A2:這是因為header改變,必需重新產生header file,修改函式庫原始碼後,再重新編譯函式庫。注意的是,產生header file的指令必須加上package name,例如:javah -jni [PACKAGE].[CLASSNAME]。
註:JNIEnv和jobject這兩個變數是無法傳到native中別的thread或process,也就是你不能在不同thread中使用env和obj這兩個變數。