温馨提示
写博客是为了记录在开发过程中所涉及到的技术以及遇到的问题的解决,如果该博客对您有所帮助,希望可以点个关注/喜欢;如果您对文章中的内容有什么不同的见解,欢迎留言进行讨论。谢谢!
JNI 开发流程
一、C 语言执行的流程
- 编辑:编写代码的过程。
- 预编译(预处理):为编译做准备工作,完成代码文本的替换工作。
- 编译:形成目标代码(.obj)。
- 连接:将目标代码与C 函数库连接合并,形成最终的可执行文件。
- 执行:执行可执行文件。
二、头文件
1、头文件的作用
头文件告诉编译器有这样一个函数,连接器负责找到这个函数的实现
2、自定义头文件
*注:开发工具为 Visual Studio 2017
1、创建 .h 文件,对相应方法进行声明。
例如:创建 math.h
#ifndef _MATH_H //如果没有定义 _MATH_H 标识
#define _MATH_H //定义 _MATH_H 标识
int add(int, int, int);
#endif
或
//该头文件只被包含一次,让编译器自己处理好循环包含问题
#pragma once
int add(int, int, int);
2、在 .h 文件同级目录下创建对应的 .c 文件,对 .h 文件中声明的方法进行实现。
例如:创建 head.c
#define _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <stdio.h>
int add(int a, int b, int c){
int result = 0;
result = a + b + c;
return result;
}
3、创建一个C文件,进行验证头文件是否编写成功。
例如:创建 test.c
#include<stdio.h>
#include "math.h"
void main(){
int a = 3, b = 4, c = 5, result = 0;
result = add(a, b, c);
printf("The result is %d!\n", result);
system("pause");
}
三、define 指令
1、define 指令的作用
- define 指令用来定义标识;
如: #ifdef __cplusplus 标识支持C++语法;防止文件重复引入 - define 指令用来定义常数;如:#define MAX 100
- define 指令用来定义“宏函数”。
如:
void jni_read(){
printf("read\n");
}
void jni_write(){
printf("write\n");
}
//宏函数
#define jin(NAME) jni_##NAME();
void main(){
jni(read);
jni(write);
getchar();
}
日志输出示例:
//__VA_ARGS__ 可变参数
#define LOG(FOTMAT,...) printf(##FOTMAT,__VA_ARGS__);printf("\n");
void main(){
LOG("%s: %d","size",99);
getchar();
}
四、JNI (Java Native Interface)
1、定义
**Java 调用C/C++或者C/C++调用 Java 的一套 API **
2、Java调用C/C++项目开发步骤(Windows系统下)
- 编写native方法
package com.example.jni;
public class JNITest {
public native static String getStringFromC();
public static void main(String[] args){
}
}
- javah命令,生成.h文件
javah com.example.jni.JNITest
//生成 com_example_jni_JNITest.h 文件
- 复制.h头文件到CPP工程中
- 复制jni.h和jni_md.h文件到CPP工程中
- 实现.h头文件中声明的函数;C函数名称:Java_完整类名_函数名
//JNITest.c
#include "com_example_jni_JNITest.h"
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getStringFromC
(JNIEnv *jEnv, jclass jcls) {
//简单实现,将C的字符传转成Java的字符串
return (*jEnv)->NewStringUTF(jEnv, "C String");
}
- 生成动态库.dll文件(Windows环境下默认dll,Linux环境下默认为so)
- 配置D:\dll 目录到环境变量,并将刚刚生成的 .dll 文件复制到D:\dll 目录下;或者复制到项目根目录下;
- 重启Eclipse,使用IDEA的需要在项目运行配置中的 VM options 中增加配置:
// VM options:
-Djava.library.path=D:\dll
五、JNIEnv
1、JNIEnv 是什么
- 在C语言中JNIEnv是一个结构体指针,代表Java运行环境,主要是调用Java中的代码,在上面JNITest.c中实现函数声明的时候,jEnv 是一个二级指针
//JNITest.c
#include "com_example_jni_JNITest.h"
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getStringFromC
(JNIEnv *jEnv, jclass jcls) {
//简单实现,将C的字符传转成Java的字符串
return (*jEnv)->NewStringUTF(jEnv, "C String");
}
- 在C++中JNIEnv是一个结构体的别名,代表Java运行环境,主要是调用Java中的代码,jEnv 是一个结构体的一级指针
//JNITest.cpp
#include "com_example_jni_JNITest.h"
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getStringFromC
(JNIEnv *jEnv, jclass jcls) {
//简单实现,将C的字符传转成Java的字符串
return jEnv->NewStringUTF("C String");
}
模拟C 的实现
//JNIEnv 是结构体指针的别名
typedef struct JNINativeInferface_* JNIEnv;
//结构体
struct JNINativeInferface_{
char* (*NewStringUTF)(JNIEnv*,char*);
};
//函数实现
char* NewStringUTF(JNIEnv* env,char* str){
return str;
}
void main(){
//实例化结构体
struct JNINativeInferface_ struct_env;
struct_env.NewStringUTF = NewStringUTF;
//结构体指针
JNIEnv e = &struct_env;
//结构体的二级指针
JNIEnv *env = &e;
//通过二级指针调用函数
char* str = (*env)->NewStringUTF(env,"Hello");
printf("str = %s\n",str);
getchar();
}
2、JNIEnv 调用函数时C和C++的区别
- C 中需要传入 JNIEnv ,因为函数执行过程中需要 JNIEnv
- C++ 中不需要传入 JNIEnv ,是因为C++中有 this,相当与JNIEnv
- C++只是针对C的那一套进行分装,给一个变量赋值为指针,这个变量是二级指针
六、jclass
每个native函数(C中的函数),都至少有两个参数(JNIEnv* jclass或者jobject)。
- 当native方法为静态方法时:jclass代表native 方法所属类的class对象(JNITest.class);
- 当 native 方法为非静态方法时:jobject 代表 native 方法所属的对象。