基于Eclipse +VisualStudio的 JNI开发,java调用C语言功能。
C语言打包生成.dll动态库,给Eclipse调用。
1.JNI的开发流程
- 获取JNI层的秘钥。
- 写好本地的native方法;
public class NdkSimple {
public static void main(String[] args) {
NdkSimple ndkSimple = new NdkSimple();
String signaturePwd = ndkSimple.getSingnaturePassword();
System.out.println("密码:" + signaturePwd); // 打印出JNI层设置的密码。密码:940223
}
// 定义本地的native方法
public native String getSingnaturePassword();
}
- 使用命令,生成头文件.h; Eclipse项目
cd 到src的目录,命令:javah能看到命令集合。
javah -d ../jni -jni com.tom.myjni.mds.jni.NdkSimple
javah -d ../jni -jni com.darren.ndk12.NdkSimple - 生成.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"// "" 引入自己工程的头文件 <> 引入系统的头文件
/* Header for class com_darren_ndk12_NdkSimple */
// 用来打一个标记,c在编译的时候会把头文件 copy 到你引入的地方,不管是重复引用还是相互引用都只会 copy 一次
// 避免互相引用文件 死循环。 如果没定义就定义。只拷贝一次。
#ifndef _Included_com_darren_ndk12_NdkSimple
#define _Included_com_darren_ndk12_NdkSimple
#ifdef __cplusplus // 相当于 if 语句 c++
// 不管是 c 还是 c++ 统一都是采用 c 的编译方式,因为在c里面是不允许函数重载的,但是在 c++ 里面可以
// 在jni开发的时候,找到函数,要确保函数是唯一的。不允许函数的重载。
extern "C" {
#endif
/* 方法的声明:
* Class: com_darren_ndk12_NdkSimple
* Method: getSingnaturePassword
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
- 将.h文件拷贝到VS项目头文件中; 将jni.h(jdk\include\win32), jni_md.h(jdk\include)
也拷贝进来。先拷贝到工程目录,然后头文件--添加--现有项--选择jni.h文件。
把include 的头文件引用(jni.h, jni_md.h),改成""引入。 依赖进来。 - 在Simple.c 中写native的实现类。实现头文件的native方法。
// 实现native方法。
// jni函数的实现,JNIEXPORT JNI的关键字,不能少,编译能通过,标记为该方法可以被外部调用。
// jstring: java中的String,返回值
// JNICALL: 也是关键字,可以省略。代表是jni调用的。
// JNIEnv: C 和Java 相互调用的桥梁。所有Function要高清。
// jobject: Java传下来的对象,就是本项目中的JniSimple.java 对象。
// jclass: java传递下来的 class对象,就是本项目中的 JniSimple.class对象。针对static native方法。
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env,"940223");
}
- 生成.dll动态库。
项目属性页:配置属性--常规--常规属性--配置类型(动态库.dll)--确定。
生成--配置管理器 -- 活动解决方案平台--x64--确定。
生成--生成解决方案。
进入项目目录--x64--Debug--,已生成NDK_Day12.dll 动态库文件。
7.进入java使用,引入加载我们的动态库。
public class NdkSimple {
//...
// 定义本地的native方法
public native String getSingnaturePassword();
static {
// System.loadLibrary: Android加载apk中的libs目录下的.so库;
// System.load: 加载一个具体路径上的.so库(服务器上下载的so库)。
String filePath = "C:/Users/NDK/NDK_DAY12/x64/Debug/DK_Day12.dll";
System.load(filePath);
}
}
2.详解.h文件和实现文件
见上面代码中的详细注释。
3.JNIEnv的实现原理
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
return (*env)->NewStringUTF(env,"940223"); // 二级指针
}
加一个* 代表把指针类型,变成 JNIEnv。
结构体不能用箭头,只有指针才能使用箭头。
typedef const struct JNINativeInterface_ *JNIEnv;
// JNIEnv 是什么:结构体 指针的别名,JNIEnv 其实是 JNINativeInterface_ 的指针别名。
其实就是一个 一级指针了。
JNIEnv * env; // 其实已经是一个二级指针了。箭头调用只能在一级指针下才能调用(->)
(env) 在二级指针上用,取到一级指针。只有就能调用箭头。
方法指针: jint (JNICALL *GetVersion)(JNIEnv * env);
java调用C语言,
生成JNIEnv对象,建立【桥梁】,才能把JNIEnv对象,传入到方法里面。
调用方法后,会还有个返回值,返回值也会先进入桥梁,再返回给调用者。
JNIEnv原理流程。Java调用C的桥梁。
模拟JNIEnv对象是怎么构建的,
#include <stdlib.h>
#include <stdio.h>
// 定义一个结构体指针的别名
typedef const struct JNINativeInterface *JNIEnv;
// 模拟一个结构体
struct JNINativeInterface{
// 结构体的方法指针定义:方法指针的名字-NewStringUTF,里面并没有方法的实现(只有方法的指针)。
char* (*NewStringUTF)(JNIEnv*, char*);
};
// 定义方法的实现:c_str -> c_str
char* NewStringUTF(JNIEnv* env, char* c_str){
// c_str -> jstring
return c_str;
}
char* Java_com_darren_getSingnaturePassword(JNIEnv * env){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env, "940223");
}
void main() {
// 构建 JNIEnv* 对象
struct JNINativeInterface nativeInterface;
// 给结构方法指针进行赋值 (指定方法的实现)
nativeInterface.NewStringUTF = NewStringUTF;
// 传给 Java_com_darren_ndk12_NdkSimple_getSingnaturePassword 的参数是 JNIEnv*
JNIEnv env = &nativeInterface;// 一级指针
JNIEnv* jniEnv = &env;// 二级指针(一级指针再取地址),建立好的桥梁(JNIEnv),二级指针传递给方法
// 把 jniEnv 对象传给 Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
char* jstring = Java_com_darren_getSingnaturePassword(jniEnv);
// jstring 通过 JNIEnv桥梁 可以再传给 java 层,这里直接打印出来。
printf("jstring = %s", jstring);
getchar();
}
4.C中访问和修改Java属性和方法
签名,授权加密。
package com.darren.ndk12;
import java.util.UUID;
public class NdkSimple1 {
public String name = "Darren"; // name => Jack
public static int age = 24;// 24 + 12
public int add(int number1, int number2){ // 定义Java方法
return number1 +number2;
}
// 小的思考:静态获取 uuid 的方法,然后再 c 调用这个方法获取uuid(Java来实现更简单)
public static String getUUID(){
return UUID.randomUUID().toString();
}
public static void main(String[] args) {
NdkSimple1 ndSimple1 = new NdkSimple1();
System.out.println("修改前:"+ndSimple1.name);
ndSimple1.changeName(); // ndk方法修改name
System.out.println("修改后:"+ndSimple1.name);
System.out.println("修改前:"+NdkSimple1.age);
changeAge(); // ndk方法修改age
System.out.println("修改后:"+NdkSimple1.age);
ndSimple1.callAddMathod(); // 调用java的方法
}
// 改变我们属性: 名称、年龄
public native void changeName();
public static native void changeAge(); // 静态方法
// c 调用 java 对象方法
public native void callAddMathod();
static{
// 引入加载我们的动态库
// System.loadLibrary :android 加载apk中的libs目录下 .so 库
// System.load : 加载一个具体路径上的 .so 库,去服务器上下载再进行加载(data/data)
System.load("C:/Users/hcDarren/Desktop/android/NDK/NDK_Day12/x64/Debug/NDK_Day12.dll");
}
}
Eclipse+cmd命令:生成头文件.h(com_darren_ndk12_NdkSimple1.h)
拷贝.h文件到VisualStudio中,添加现有项 NdkSimple1.h
在Simple.c 中,写实现方法。
// 实现我们的 native 方法
#include "com_darren_ndk12_NdkSimple.h"
#include "com_darren_ndk12_NdkSimple1.h" // 添加新的.h文件
// JNIEXPORT JNI 一个关键字,不能少(编译能通过),标记为该方法可以被外部调用
// jstring : 代表 java 中的 String
// JNICALL: 也是一个关键字,可以少的 jni call
// JNIEnv: 这个是 c 和 java 相互调用的桥梁,所有 function 搞清
// jobject: java传递下来的对象,就是本项目中 JniSimple java 对象
// jclass: java传递下来的 class 对象,就是本项目中的 JniSimple.class
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env,"940223");
}
// C中访问和修改Java属性1:修改java的属性
JNIEXPORT void JNICALL Java_com_darren_ndk12_NdkSimple1_changeName
(JNIEnv *env, jobject jobj) {
// 获取 name 属性然后修改为 Jack
// 3.获取 jclass
jclass j_clz = (*env)->GetObjectClass(env, jobj);
// 获取 jfieldId (JNIEnv *env, jclass clazz, const char *name, const char *sig)
// name 获取哪个属性的属性名
// 2.sig 属性的签名
jfieldID j_fid = (*env)->GetFieldID(env, j_clz, "name", "Ljava/lang/String;");
// 1.获取 name 属性的值
jstring j_str = (*env)->GetObjectField(env, jobj, j_fid);
// 打印字符串 jstring -> c_str
char* c_str = (*env)->GetStringUTFChars(env,j_str,NULL);
printf("name is %s", c_str);
// 修改成 jack
jstring jackName = (*env)->NewStringUTF(env,"Jack");
(*env)->SetObjectField(env, jobj, j_fid, jackName);
}
// C中访问和修改Java属性2,静态方法第二个参数是jclass
JNIEXPORT void JNICALL Java_com_darren_ndk12_NdkSimple1_changeAge
(JNIEnv * env, jclass jcls){
// 1.首先获取原来的值
jfieldID j_fid = (*env)->GetStaticFieldID(env,jcls,"age","I");
// Static 获取静态的
jint age = (*env)->GetStaticIntField(env, jcls, j_fid);
// jint -> int。不需要转换
age += 12;
// 2.设置新的 age 给参数
(*env)->SetStaticIntField(env,jcls,j_fid,age);
}
// C中访问Java的对象方法, 对象调用
JNIEXPORT void JNICALL Java_com_darren_ndk12_NdkSimple1_callAddMathod
(JNIEnv *env, jobject jobj){
jclass j_clz = (*env)->GetObjectClass(env,jobj);
// 获取 methodid,方法的名字 add, 方法签名 (II)I
jmethodID j_mid = (*env)->GetMethodID(env, j_clz, "add", "(II)I");
// 去调用 java 的方法,拿到返回值,传入加数 和 被加数
jint sum = (*env)->CallIntMethod(env, jobj, j_mid,2,3);
printf("sum = %d",sum); // 拿到结果,并打印:5
}
通过cmd 命令,拿到方法的签名。
......NDK_Day12\src> cd ..
......NDK_Day12> cd bin
......NDK_Day12\bin> javap -p -s com.darren.ndk12.NdkSimple1
进而得到所有方法和属性的签名。
Compiled from "NdkSimple1.java"
public class com.darren.ndk12.NdkSimple1 {
public java.lang.String name;
descriptor: Ljava/lang/String; //属性name的签名
public static int age;
descriptor: I //属性age的签名
static {};
descriptor: ()V
public com.darren.ndk12.NdkSimple1();
descriptor: ()V
public int add(int, int);
descriptor: (II)I //方法的签名
public static java.lang.String getUUID();
descriptor: ()Ljava/lang/String;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
public native void changeName();
descriptor: ()V
public static native void changeAge();
descriptor: ()V
public native void callAddMathod();
descriptor: ()V
}
4.C中访问 java的static方法。
在JNI中调用Java的静态方法,你需要使用JNIEnv提供的FindClass、GetStaticMethodID和CallStaticVoidMethod
(或其他对应的CallStaticXXXMethod)函数。
以下是一个简单的例子,假设我们有一个Java类TestClass,它有一个静态方法staticMethod,我们将在C中调用它。
这个例子中,Java_com_example_yourapp_YourActivity_callStaticMethod是JNI层的函数,
它会被Java代码(比如一个Activity)调用,从而触发对Java静态方法的调用。
记得在实际的应用中,你需要使用正确的包名和类名来替换com/example/yourapp和TestClass。
public class TestClass {
public static void staticMethod(String message) {
System.out.println("Static method called with: " + message);
}
}
#include <jni.h>
#include <stdio.h>
JNIEXPORT void JNICALL
Java_com_example_yourapp_YourActivity_callStaticMethod(JNIEnv *env, jobject obj) {
jclass clazz = (*env)->FindClass(env, "com/example/yourapp/TestClass");
if (clazz == NULL) {
return; // class not found
}
jmethodID methodID = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "(Ljava/lang/String;)V");
if (methodID == NULL) {
return; // method not found
}
jstring message = (*env)->NewStringUTF(env, "Hello from JNI!");
(*env)->CallStaticVoidMethod(env, clazz, methodID, message);
(*env)->DeleteLocalRef(env, clazz);
(*env)->DeleteLocalRef(env, message);
}
不怕没有工作,不怕工资低。多花时间在技术上,多思考。
网友评论