最近写了一个网站模拟点击的项目,究其原因是合作方提供不了接口。利用httpclient模拟浏览器访问,深深的感到这事的蛋疼性。然后蛋疼的事还不止这个,做的是一个远在千里之外内网的一个环境,远程调试又卡的要命。经过漫长的搜集数据和尝试,这个接口项目总算是结束了。 公司考虑到提供出去的接口一反编译就暴露在阳光下了,所有才有了JNI。
废话不说了,进入正题。
Jni程序开发的一般操作步骤如下:
l 编写java中的调用类
l 用javah生成c/c++原生函数的头文件
l c/c++中调用需要的其他函数功能,实现原生函数(原则上可以调用任何资源)
l 将项目依赖的所有原生库和资源加入到java项目的java.library.path
l 生成java程序
l 发布java应用和dll或者so
首先,建立一个测试的类
package com.ist.manage.ess;
public class BssSupport { static { System.loadLibrary("BssSupport"); } public static void main(String[] args) { BssSupport bs = new BssSupport(); bs.test("hello,java"); } public void imi(String s) { System.out.println(s); } public native void test(String instring); }打开cmd进入到工程的目录下
javah -encoding UTF-8 -jni com.ist.manage.ess.TestHello //因为项目本来是utf8编码的缘故所有加上utf8,产生一个.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #ifndef _Included_com_ist_manage_ess_TestHello #define _Included_com_ist_manage_ess_TestHello #ifdef __cplusplus extern "C" { #endif /* * Class: com_ist_manage_ess_TestHello * Method: test * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_ist_manage_ess_BssSupport_test(JNIEnv *, jobject,jstring ); #ifdef __cplusplus } #endif #endif
观察一下就能得出函数是Java +报名 +函数名组成,中间中下划线分割,如果函数名中包含下划线就会被特殊处理,所以还是不要用那么蛋疼的命名规则,其实也很简单,再说java的函数名,一般不加下划线的,注意一下。
还看到了生僻的类型,这些类型是jni特有的和C++有一一对应的关系。
Java类型 | 本地类型 | JNI中定义的别名 |
int | long | jint |
long | _int64 | jlong |
byte | signed char | jbyte |
boolean | unsigned char | jboolean |
char | unsigned short | jchar |
short | short | jshort |
float | float | jfloat |
double | double | jdouble |
Object | _jobject* | jobject |
需要在jdk目录下
在%JAVA_HOME%/include/下找到 jni.h,
在%JAVA_HOME%/include/win32/下找到jni_md.h,
可以复制到vs/vc的include/”下,这样就不用费力的在工程中导入这两文件了。
vc不能编译出64位的dll,果断用了vs2010,发现不支持C99这也是一件坑爹的地方。
vs建工程很简单,新建一个空的dll工程即可,省的输入一大堆命令编译。
建一个.cpp文件,这里代码很简单,先包含头文件,然后通过jni提供的函数调用java函数imi()
#include "com_ist_manage_ess_TestHello.h" JNIEXPORT void JNICALL Java_com_ist_manage_ess_BssSupport_test(JNIEnv *env, jobject obj,jstring in_url) { jclass class_Test =env->GetObjectClass(obj); jmethodID mid = env->GetMethodID(class_Test, "imi","(Ljava/lang/String;)V" ); env->CallObjectMethod(obj, mid,in_url); }这里比较蛋疼的问题是jni签名,为什么会有这个东西,以为java支持函数重载,编译器必须知道函数的形参类型才能到找到具体的函数。
然后,得说明一下这些函数参数和签名的对应关系才好下手。
类型标示 |
Java类型 |
类型标示 |
Java类型 |
Z |
boolean |
F |
float |
B |
byte |
D |
double |
C |
char |
Ljava/lang/String |
String |
S |
short |
[I |
Int[] |
I |
int |
[Ljava/lang/object |
Object[] |
J |
long |
上面表格中列出了常用的类型标示,如果java类型是数组,则标示中会出现一个“[”,另外引用类型(除基本类型的数组外)的标识最后都有一个”;”分号
函数签名一开始最好自己手动输入理解一下熟练后可以利用java自带的工具自动生成
javap -s -p xxx
Xxx为编译后的class文件,s标识输出内部数据类型的签名信息,p表示打印所有的函数和成员的签名信息,默认只会打印public成员和函数的签名信息
类的签名规则是:“L+全限定类名+;” 三部分组成,例如:
- long fun (int n, String str, int[] arr);
- (ILjava/lang/String;[I)J
括号里面的内容分成三部分,之间没有空格,
即“I”、“Ljava/lang/String;”和“[I”,
分别代表 int、String和int[]。括号外面是返回值类型签名,J代表long型。
这里还有必要解释一下参数1和参数2 ,参数1就是jni环境了,可以用一张图表示。参数2就是调用JNI的那个对象了,如果是静态方法变对应的类。写一个静态的函数,javah一下就看到了参数名就不叫object了,但是类型都是一样的。
相信代码一看就懂,编译一下把生产的.dll扔到 c:/window/systm32目录下(path自己加也太麻烦了,就干脆用这个目录了)
然后运行java程序就看到了效果。