1、Linux下JNI的使用()
Linux下 JNI的使用
学习Android其中涉及对JNI的使用,对于这种跨语言的调用真没有见过,
Java也都是最近才学的更别说对JNI的了解了,
JNI的使用对于Android来说又是十分的重要和关键。那么到底Java到底是如何调用C/C++的,
通过网络达人的总结中学习,自己也顺便总结一下这个学习的过程。
什么是JNI
JNI是Java native interface的简写,可以译作Java原生接口。
Java可以通过JNI调用C/C++的库,这对于那些对性能要求比较高的Java程序无疑是一个福音。
JNI是Java与C/C++交互的接口。
使用JNI也是有代价。大家都知道JAVA程序是运行在JVM之上的,可以做到平台无关。
但是如果Java程序通过JNI调用了原生的代码(比如c/c++等),则Java程序就丧失了平台无关性。
最起码需要重新编译原生代码部分。所以应用JNI需要好好权衡,不到万不得已,请不要选择JNI,
可以选择替代方案,比如TCP/IP进行进程间通讯等等。这也是为什么谷歌的Android平台的底层虽然用JNI实现,
但是他不建议开发人员用JNI来开发Android上面的应用的原因。将会丧失Android上面的应用程序平台无关性。
代码实例
下面以HelloWorld的实现学习Linux下 JNI的使用。
第一步:
创建一个 TestJni.java文件
import java.util.*;public class TestJni{ //声明原生函数:参数为String类型 public native void print(String content); //加载本地库代码 static { System.loadLibrary("TestJni"); }}
编译 TestJni.java文件:javac TestJni.java
在当前文件夹下生成TestJni.class文件
注意print方法的声明,关键字native表明该方法是一个原生代码实现的。
另外注意static代码段的System.loadLibrary调用,这段代码表示在程序加载的时候,自动加载libTestJni.so库。
第二步:
生成 TestJni.h文件
执行命令:javah -jni TestJni
生成TestJni.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */#include/* Header for class TestJni */#ifndef _Included_TestJni#define _Included_TestJni#ifdef __cplusplusextern "C" {#endif/* * Class: TestJni * Method: print * Signature: (Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_TestJni_print (JNIEnv *, jobject, jstring);#ifdef __cplusplus}#endif#endif
该文件中包含了一个函数Java_TestJni_print的声明。这里面自动包含两个参数,非常重要。JNIEnv *和 jobject
第三步:
创建TestJni.c文件
#include#include #include JNIEXPORT void JNICALL Java_TestJni_print(JNIEnv *env,jobject obj, jstring content){ // 从 instring 字符串取得指向字符串 UTF 编码的指针 //注意C语言必须(*env)-> C++ env-> const jbyte *str = (const jbyte *)(*env)->GetStringUTFChars(env,content, JNI_FALSE); printf("Hello---->%s\n",str); // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。 (*env)->ReleaseStringUTFChars(env, content, (const char *)str ); return;}
//这里看到 JNIEnv作用了,使得我们可以使用Java的方法
//jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction 的一个句柄,相当于 this 指针
参数类型 jstring 对应java中的String,这里是有所不同的。每一个Java里的类型这里有对应的与之匹配。
命令行输入:
cc -I/usr/lib/jvm/java-6-sun/include/linux/
-I/usr/lib/jvm/java-6-sun/include/
-I/home/xmp/AndroidProject/apk/JNI
-fPIC -shared -o libTestJni.so TestJni.c
生成:libTestJni.so库文件
在当前目录生成libTestJni.so。注意一定需要包含Java的include目录(请根据自己系统环境设定),
因为libTestJni.c中包含了jni.h。另外一个值得注意的是在libTestJni.java中我们LoadLibrary方法加载的是“TestJni”,
可我们生成的Library却是libTestJni。这是Linux的链接规定的,
一个库的必须要是:lib+库名+.so。链接的时候只需要提供库名就可以了
-I/home/xmp/AndroidProject/apk/JNI 是我自己的练习目录也必须包含,否则.c文件中会找不到TestJni.h头文件。
现在 liblibTestJni.so就是一个可以使用的库了,其功能就是有一个print函数 与刚才所创建的TestJni.java文件对应,
TestJni.java类中加载库liblibTestJni.so,声明了其函数print。所以现在TestJni.java中具备使用print函数的功能;
所以现在我们就可以通过使用TestJni.java来使用调用C库libTestJni.so中的函数
当然现在任何java类都可已加载liblibTestJni.so库来使用其中的功能。
第四步:
创建HelloWord.java函数
import java.util.*;public class HelloWorld{ public static void main(String argv[]) { new HelloWorld(); } public HelloWorld() { new TestJni().print("Hello,World !"); //调用TestJni的原生函数print }}
输入命令编译: javac HelloWorld.java
生成HelloWorld.class
第五步:
运行HelloWorld程序
命令行输入:java HelloWorld
输出结果:Hello---->Hello,World !
验证OK!
如果你这步发生问题,如果这步你收到java.lang.UnsatisfiedLinkError异常,可以通过如下方式指明共享库的路径:
java -Djava.library.path='.' HelloWorld
或者输入命令:
export LD_LIBRARY_PATH=“HelloWorld路径”:$LD_LIBRARY_PATH 设置环境变量
然后再 java HelloWorld 一样OK
简单例子,照着以下参考文档即可实现。
参考文档:
JNIEnv功能参考:
2、linux动态链接库导出函数控制()
windows 环境的vc的话,可以方便的指定__declspec(dllexport) 关键字来控制是否把dll中的函数导出。
我也来测试一下linux下面是如何做的:先看gcc 和ld的相关选项======================================
gcc 选项 -shared Produce a shared object which can then be linked with other objects to form an executable. Not all systems support this option. For predictable results, you must also specify the same set of options that were used to generate code (-fpic, -fPIC, or model suboptions) when you specify this option.[1] -fpic Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine. Such code accesses all constant addresses through a global offset table (GOT). The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system). If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)Position-independent code requires special support, and therefore
works only on certain machines. For the 386, GCC supports PIC for System V but not for the Sun 386i. Code generated for the IBM RS/6000 is always position-independent.When this flag is set, the macros "__pic__" and "__PIC__" are
defined to 1.-fPIC
If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC.Position-independent code requires special support, and therefore
works only on certain machines.When this flag is set, the macros "__pic__" and "__PIC__" are
defined to 2.-rdynamic
Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of "dlopen" or to allow obtaining backtraces from within a program. -fvisibility=default|internal|hidden|protected Set the default ELF image symbol visibility to the specified option---all symbols will be marked with this unless overridden within the code. Using this feature can very substantially improve linking and load times of shared object libraries, produce more optimized code, provide near-perfect API export and prevent symbol clashes. It is strongly recommended that you use this in any shared objects you distribute.Despite the nomenclature, "default" always means public ie;
available to be linked against from outside the shared object. "protected" and "internal" are pretty useless in real-world usage so the only other commonly used option will be "hidden". The default if -fvisibility isn't specified is "default", i.e., make every symbol public---this causes the same behavior as previous versions of GCC.A good explanation of the benefits offered by ensuring ELF symbols
have the correct visibility is given by "How To Write Shared Libraries" by Ulrich Drepper (which can be found at < a superior solution made possible by this option to marking things hidden when the default is public is to make the default hidden and mark things public. This is the norm with DLL's on Windows and with -fvisibility=hidden and "__attribute__ ((visibility("default")))" instead of "__declspec(dllexport)" you get almost identical semantics with identical syntax. This is a great boon to those working with cross-platform projects. For those adding visibility support to existing code, you may find #pragma GCC visibility of use. This works by you enclosing the declarations you wish to set visibility for with (for example) #pragma GCC visibility push(hidden) and #pragma GCC visibility pop. Bear in mind that symbol visibility should be viewed as part of the API interface contract and thus all new code should always specify visibility when it is not the default ie; declarations only for use within the local DSO should always be marked explicitly as hidden as so to avoid PLT indirection overheads---making this abundantly clear also aids readability and self-documentation of the code. Note that due to ISO C++ specification requirements, operator new and operator delete must always be of default visibility.Be aware that headers from outside your project, in particular
system headers and headers from any other library you use, may not be expecting to be compiled with visibility other than the default. You may need to explicitly say #pragma GCC visibility push(default) before including any such headers.extern declarations are not affected by -fvisibility, so a lot of
code can be recompiled with -fvisibility=hidden with no modifications. However, this means that calls to extern functions with no explicit visibility will use the PLT, so it is more effective to use __attribute ((visibility)) and/or #pragma GCC visibility to tell the compiler which extern declarations should be treated as hidden.Note that -fvisibility does affect C++ vague linkage entities. This
means that, for instance, an exception class that will be thrown between DSOs must be explicitly marked with default visibility so that the type_info nodes will be unified between the DSOs.An overview of these techniques, their benefits and how to use them
is at <>.===================================
ld命令选项 -E --export-dynamic --no-export-dynamic When creating a dynamically linked executable, using the -E option or the --export-dynamic option causes the linker to add all symbols to the dynamic symbol table. The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.If you do not use either of these options (or use the
--no-export-dynamic option to restore the default behavior), the dynamic symbol table will normally contain only those symbols which are referenced by some dynamic object mentioned in the link.If you use "dlopen" to load a dynamic object which needs to refer
back to the symbols defined by the program, rather than some other dynamic object, then you will probably need to use this option when linking the program itself.You can also use the dynamic list to control what symbols should be
added to the dynamic symbol table if the output format supports it. See the description of --dynamic-list.Note that this option is specific to ELF targeted ports. PE
targets support a similar function to export all symbols from a DLL or EXE; see the description of --export-all-symbols below. --dynamic-list=dynamic-list-file Specify the name of a dynamic list file to the linker. This is typically used when creating shared libraries to specify a list of global symbols whose references shouldn't be bound to the definition within the shared library, or creating dynamically linked executables to specify a list of symbols which should be added to the symbol table in the executable. This option is only meaningful on ELF platforms which support shared libraries.The format of the dynamic list is the same as the version node
without scope and node name. See VERSION for more information.--dynamic-list-data
Include all global data symbols to the dynamic list. --version-script=version-scriptfile Specify the name of a version script to the linker. This is typically used when creating shared libraries to specify additional information about the version hierarchy for the library being created. This option is only fully supported on ELF platforms which support shared libraries; see VERSION. It is partially supported on PE platforms, which can use version scripts to filter symbol visibility in auto-export mode: any symbols marked local in the version script will not be exported.#VERSION 文件格式可以参考这里
========================================= 测试看看: -----------------------main.c -----------------------------------#include<stdio.h>#include<string.h>#include<stdlib.h>#include<dlfcn.h>
int test (int i)
{printf("i=%d\n" ,i);
}int main()
{ int (*test2)(int); int (*test3)(int); int *handler;
handler=(int*)dlopen("test.so", RTLD_LAZY);
if (!handler) { printf( "加载模块错误 %s\n", dlerror() ); return; }test3 = dlsym(handler, "test3");
if (test3) test3(3);test2 = dlsym(handler, "test2");
if (test2) test2(2);dlclose(handler);
exit(0);
}
------------------------------------------------------------------
$ gcc -rdynamic main.c -o main -ldl$ readelf -s main
Symbol table '.dynsym' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 3: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND (3) 5: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 6: 00000000 0 FUNC GLOBAL DEFAULT UND (4) 7: 00000000 0 FUNC GLOBAL DEFAULT UND (3) 8: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 9: 00000000 0 FUNC GLOBAL DEFAULT UND (3) 10: 0804a020 0 NOTYPE GLOBAL DEFAULT 24 __data_start 11: 0804a028 22 OBJECT GLOBAL DEFAULT 24 str 12: 0804a048 0 NOTYPE GLOBAL DEFAULT ABS _end 13: 0804a040 0 NOTYPE GLOBAL DEFAULT ABS _edata 14: 0804a020 0 NOTYPE WEAK DEFAULT 24 data_start 15: 080486b0 0 FUNC GLOBAL DEFAULT 14 _start 16: 080488f8 4 OBJECT GLOBAL DEFAULT 16 _fp_hw 17: 080488fc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used 18: 08048850 90 FUNC GLOBAL DEFAULT 14 __libc_csu_init 19: 0804a040 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 20: 08048764 28 FUNC GLOBAL DEFAULT 14 test ///test 在这里导出了 21: 08048780 178 FUNC GLOBAL DEFAULT 14 main 22: 080485ec 0 FUNC GLOBAL DEFAULT 12 _init 23: 08048840 5 FUNC GLOBAL DEFAULT 14 __libc_csu_fini 24: 080488dc 0 FUNC GLOBAL DEFAULT 15 _fini ----------------------------------------------------------------------------------$ gcc main.c -o main -ldl
$ readelf -s main
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 3: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND (3) 5: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 6: 00000000 0 FUNC GLOBAL DEFAULT UND (4) 7: 00000000 0 FUNC GLOBAL DEFAULT UND (3) 8: 00000000 0 FUNC GLOBAL DEFAULT UND (2) ///没有把 test函数导出 9: 00000000 0 FUNC GLOBAL DEFAULT UND (3) 10: 080486fc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used ====================================================================$ gcc -rdynamic -fPIC main.c -o main -ldl$ readelf -s main
可以看到有 '.dynsym' 节有把函数导出
=======================================================================
------test.c---------------------
#include<stdio.h>#include<string.h>#include<stdlib.h> extern int test (int i);int test2 (int i)
{ test(i); printf("this is test2\n");}int test3 (int i)
{ printf("this is test 3\n");}-----------------------------------------------$ gcc -shared -fPIC test.c -o test.so
$ readelf -s test.so'.dynsym' 节里把所有函数导出
====================================================================
$ ./main
加载模块错误 test.so: cannot open shared object file: No such file or directory找不到 test.so文件,他不会自动在当前目录下搜索啊。
那就设置好这个LD_LIBRARY_PATH这个环境变量吧,我是不想复制到系统目录去了$ echo $LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH=`pwd`
$ echo $LD_LIBRARY_PATH/home/widebright/桌面/测试用例--------------------------
gcc -rdynamic -fPIC main.c -o main -ldlgcc -shared -fPIC test.c -o test.so使用上面编译选线,结果 $ ./mainthis is test 3i=2this is test2--------------------------------gcc -fPIC main.c -o main -ldlgcc -shared -fPIC test.c -o test.so使用上面编译选线,结果test.so就找不到 main中export出来的test函数了$ ./main this is test 3./main: symbol lookup error: /home/widebright/桌面/测试用例/test.so: undefined symbol: test--------------------------------gcc -rdynamic main.c -o main -ldlgcc -shared -fPIC test.c -o test.so使用上面编译选线,结果还是可以执行,说明 -fPIC对程序来说好像用处不大。$ ./mainthis is test 3i=2this is test2------------------------------------------------gcc -rdynamic main.c -o main -ldlgcc -shared test.c -o test.so使用上面编译选线,也还能正确运行 -fPIC 有什么作用啊,不明显$ ./mainthis is test 3i=2this is test2---------------------------------------------
----visibility.txt------------------
{ global: test3; local: *;};-------------------gcc -rdynamic main.c -o main -ldl
gcc -shared test.c -o test.so -Wl,--version-script=visibility.txt$ gcc -shared test.c -o test.so -Wl,--version-script=visibility.txt
$ ./main this is test 3可以看到 使用--version-script=visibility.txt 参数控制之后,只有test3才被导出了
:~/桌面/测试readelf -s test.so
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 3: 00000000 0 NOTYPE GLOBAL DEFAULT UND test 4: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 5: 00000000 0 FUNC WEAK DEFAULT UND (3) 6: 0000041b 20 FUNC GLOBAL DEFAULT 12 test3 //visibility.txt文件里面指定了 test3才导出了, test2不再导出了======================================
gcc -shared test.c -o test.so -fvisibility=hidden----当 -fvisibility=hidden 设置为hidden之后,所有的都函数不再导出了。
readelf -s test.soSymbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 3: 00000000 0 NOTYPE GLOBAL DEFAULT UND test ///这个还是会在这里,知道是外部函数来的 4: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 5: 00000000 0 FUNC WEAK DEFAULT UND (3) 6: 0000202c 0 NOTYPE GLOBAL DEFAULT ABS _end 7: 00002024 0 NOTYPE GLOBAL DEFAULT ABS _edata 8: 00002024 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 9: 0000035c 0 FUNC GLOBAL DEFAULT 10 _init 10: 000004e8 0 FUNC GLOBAL DEFAULT 13 _fini==============================================
我们给要导出到函数加上 属性控制,改成这样,
----------test.c-----------------#include<stdio.h>#include<string.h>#include<stdlib.h> extern int test (int i);__attribute ((visibility)) int test2 (int i)
{ test(i); printf("this is test2\n");}__attribute ((visibility)) int test3 (int i)
{ printf("this is test 3\n");}-------------------------------
$ gcc -shared test.c -o test.so -fvisibility=hidden
$ $ $ readelf -s test.soSymbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 3: 00000000 0 NOTYPE GLOBAL DEFAULT UND test 4: 00000000 0 FUNC GLOBAL DEFAULT UND (2) 5: 00000000 0 FUNC WEAK DEFAULT UND (3) 6: 0000202c 0 NOTYPE GLOBAL DEFAULT ABS _end 7: 00002024 0 NOTYPE GLOBAL DEFAULT ABS _edata 8: 000004bc 31 FUNC GLOBAL DEFAULT 12 test2 //这次就会导出了 9: 00002024 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 10: 000003a0 0 FUNC GLOBAL DEFAULT 10 _init 11: 00000528 0 FUNC GLOBAL DEFAULT 13 _fini 12: 000004db 20 FUNC GLOBAL DEFAULT 12 test3 $ ./main this is test 3i=2this is test2-----------------------------------------------------总结:
通过gcc命令的-fvisibility=hidden 选项和 "__attribute__ ((visibility("default")))" 语法扩展 可以得到 vc中
__declspec(dllexport)"的效果。采用ld的 --version-script=version-scriptfile 参数 类似vc中到 *.DEF 文件,也可以用来统一链接库到输出与否。
测完才发现,gcc帮助文档提到的 “How To Write Shared Libraries” 这篇文章上面解释的更完整一些,一个pdf文档,在google搜索一下就可以找到了。其他的办法还包括:
1. static 定义函数2、libtool的-export-symbols参数
$ libtool --mode=link gcc -o libfoo.la \foo.lo -export-symbols=foo.sym3.使用alias属性来隐藏具体的函数名
int next (void) { return ++last_int; } extern __typeof (next) next_int __attribute ((alias ("next"), visibility ("hidden")));
3、
4、
5、
6、
7、
8、