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 方法所属的对象.
来源: http://www.jianshu.com/p/6262d5f1f374