文档章节

C语言基础及指针⑩预编译及jni.h分析

逝我
 逝我
发布于 2016/09/21 09:08
字数 1905
阅读 3
收藏 0

接续上篇C语言基础及指针⑨联合体与枚举

在上篇中我们了解了 , 多类型集合的联合体 , 固定值集合的枚举 , 内容相对比较简单 , 今天我们谈谈预编译 , 也是本系列最后一个知识点 , C语言基础系列就要告一段落了 , 要开始我们的jni系列了 , JNI(Java Native Interface) 是java与C/C++进行通信的一种技术 , 使用JNI技术,可以java调用C/C++的函数对象等等,Android中的Framework层与Native层就是采用的JNI技术 。

预编译

预编译(预处理,宏定义,宏替换)这种叫法 , 关键字#define , 其本质是替换文本。

首先我们了解一下C语言的执行过程:

  1. 编译 --> 生成目标代码(.obj)
  2. 连接 --> 将目标代码与C函数库合并 , 生成最终可执行文件
  3. 执行

预编译 , 主要在编译时期完成文本替换工作 , 常见的预编译指令有: #include,ifndef,#endif,define,#pragma once等等 。

我们在jni.h头文件中 , 可以看到较多的预编译指令 , 例如:

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

如果编译环境是C++, 则使用:

typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;

C语言编译环境 , 则使用:

typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;

这里的定义就是我们后需要结束的JNIEnv指针 , 在C++环境中JNIEnv是一个一级指针 , 但是在C语言环境中 , 他是一个二级指针 ,这个我们将在jni系列中 , 再详细说明 。

预编译示例

// 定义一个常数
#define MAX 100 

void main() {

	int i = 99;
	if (i < MAX) // 在编译时期, 会将MAX替换成100
	{
		printf("i 小于 MAX\n");
	}
}

定义一个宏常量MAX他的代表100 , 宏常数没有类型 , 只做替换。

宏函数

#define 预编译指令 , 不光可以定义常量 , 还可以定义函数 , 因为其本质是替换 , 所有可以简化很多很长的函数名称 。

在jni.h中 , 也可以看到宏函数 , 如下:

#define CALL_TYPE_METHODA(_jtype, _jname)                                   \
    __NDK_FPABI__                                                           \
    _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID,           \
        jvalue* args)                                                       \
    { return functions->Call##_jname##MethodA(this, obj, methodID, args); }

其中(_jtype, _jname)里面的_jtype , _jname都是替换标识 , 替换_jtype,##_jname##

宏函数示例

// 普通函数
void _jni_define_func_read() {
	printf("read\n");
}

void _jni_define_func_write() {
	printf("write\n");
}

// 定义宏函数
#define jni(NAME) _jni_define_func_##NAME() ;

void main() {

	// 宏函数
	//jni(read); 可以简化函数名称
    jni(write) ;

	system("pause");
}

宏函数的核心就是替换 , 只要记住这一点就够了 。下面我们就来做另一个实例 , 打印类似android Log日志的形式的函数。

// 模拟Android日志输出 , 核心就是替换
#define LOG(LEVE,FORMAT,...) printf(##LEVE); printf(##FORMAT,__VA_ARGS__) ;
#define LOGI(FORMAT,...) LOG("INFO:",##FORMAT,__VA_ARGS__) ;
#define LOGE(FORMAT,...) LOG("ERROR:",##FORMAT,__VA_ARGS__) ;
#define LOGW(FORMAT,...) LOG("WARN:",##FORMAT,__VA_ARGS__) ;

void main() {

	LOGI("%s", "自定义日志。。。。\n");
	LOGE("%s", "我是错误日志...\n");

	system("pause");
}

输出:

INFO:自定义日志。。。。
ERROR:我是错误日志...

我们可以看到输出信息前面带有类型标识 。在这里做几点代码说明:

  1. 上述代码中LEVE,FORMAT都是自定义的 , 可以是任意名称 , 只有后面替换的名称一致即可。
  1. LOG(LEVE,FORMAT,...)中的...表示可变参数 , 替换则是使用__VA_ARGS__这种固定写法 。
  2. 宏函数的核心就是——替换

预编译指令就介绍到这里 , 其中心点就是替换 。 学到这里 , C语言的基础也介绍得七七八八了 , 下面一起来分析分析 , 我们编写JNI代码的一个比较重要的头文件jni.h 。

简要分析jni.h

jni.h我们编写NDK的时候需要引入的一个头文件 , 它位于android-ndk-r10e\platforms\android-21\arch-arm\usr\include\jni.h不同的平台都有相应的jni.h 。

在文件开始部分 , 定义了很多类型别名 , 区分C++与C的编译环境 , 如下:

#ifdef __cplusplus
/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};

#else /* not __cplusplus */

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;

接着定义了对象引用的枚举 , 和方法签名的结构体:

typedef enum jobjectRefType {
    JNIInvalidRefType = 0,
    JNILocalRefType = 1,
    JNIGlobalRefType = 2,
    JNIWeakGlobalRefType = 3
} jobjectRefType;

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

接下来就是最重要的JNIEnv了 , 区分了C和C++环境

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

JNIEnv是一个结构体指针 , 里面定义了很多函数 , 有调用java方法的函数 , 转换java类型的函数 , 我可以看到 , C编译环境中 , JNIEnv是struct JNINativeInterface*的指针别名,我们来看看JNINativeInterface结构体是怎样的:

/*
 * Table of interface function pointers.
 */
struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jint        (*GetVersion)(JNIEnv *);

    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
                        jsize);
    jclass      (*FindClass)(JNIEnv*, const char*);

    jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);
    jfieldID    (*FromReflectedField)(JNIEnv*, jobject);
    /* spec doesn't show jboolean parameter */
    jobject     (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);

    jclass      (*GetSuperclass)(JNIEnv*, jclass);
    jboolean    (*IsAssignableFrom)(JNIEnv*, jclass, jclass);

大部分的操作都是在JNINativeInterface这个结构体里面,定义了很多操作函数 , 比较常见的字符处理函数:

jstring     (*NewStringUTF)(JNIEnv*, const char*);
jsize       (*GetStringUTFLength)(JNIEnv*, jstring);
/* JNI spec says this returns const jbyte*, but that's inconsistent */
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
  1. 将字符指针转换成java的String类型
  1. 得到java的String类型长度
  2. 将java的String类型转换成C的字符指针

值得注意的是 , C++的JNIEnv结构体指针 , 并没有重新实现 , 而生将C中的JNINativeInterface结构体 , 重新打了一次包 , 如下:

/*
 * C++ object wrapper.
 *
 * This is usually overlaid on a C struct whose first element is a
 * JNINativeInterface*.  We rely somewhat on compiler behavior.
 */
struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;

#if defined(__cplusplus)

    jint GetVersion()
    { return functions->GetVersion(this); }

    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }

    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

因为C++是面向对象语言 , 所有有上下文环境变量this , 简化了C语言中调用函数需要传递本身结构体指针的操作 。

分析了部分jni.h的源码 , 后续的源码 , 我相信看完C语言系列, 也可以看得懂 , 这里就不再做分析了 , 读者可自行查看源码分析一遍 ,对写jni代码还是有好处的 , 至少对JNIEnv结构体指针中的函数有一个大致的印象 。

结语

C语言基础系列 , 就宣告完结了 , 还有很多知识点没讲到 , 还有很多需要学习 , 但我们不是要把C语言学透了学精了 , 再去学JNI 和NDK, 这样 , 不知要到何年何月了 。 所以 , 先学基础了解概貌 , 将java与C结合起来 , 着手做些东西出来 , 然后再继续深入 。

语言都是相通的 , 关键是解决问题的思路 。

本文由老司机学院(动脑学院)特约提供

做一家受人尊敬的企业,做一位受人尊敬的老师

Android程序员学C系列:

C语言基础及指针①

C语言基础及指针②之指针内存分析

C语言基础及指针③函数与二级指针

C语言基础及指针④函数指针

C语言基础及指针⑤动态内存分配

C语言基础及指针⑥字符操作

C语言基础及指针⑦结构体与指针

C语言基础及指针⑧文件IO

C语言基础及指针⑨联合体与枚举

C语言基础及指针⑩预编译及jni.h分析

© 著作权归作者所有

逝我
粉丝 4
博文 20
码字总数 31426
作品 0
长沙
程序员
私信 提问
NDK开发,没有你想象的那么难

NDK:Native Development Kit原生开发工具 NDK能干什么:NDK使得在android中,java可以调用C函数库。 为什么要用NDK:我们都知道,java是半解释型语言,很容易被反汇编后拿到源代码文件,在开...

kymjs张涛
2014/03/30
0
37
在Java中调用C/C++本地库

} include <jni.h> / Header for class Sample1 / ifndef IncludedSample1 define IncludedSample1 ifdef cplusplus extern "C" { endif /* Class: Sample1 Method: intMethod Signature: (I......

刘学炜
2012/07/12
0
0
Android NDK开发之旅 目录

Android NDK开发之旅 目录 Android NDK开发之旅1--NDK介绍 Android NDK开发之旅2--C语言--基本数据类型 Android NDK开发之旅3--C语言--指针 Android NDK开发之旅4--C语言--动态内存分配 Andr...

香沙小熊
2017/12/31
0
0
嵌入式开发之C基础学习笔记02--第一个例子分析

1.C语言程序结构和书写规范 系统头文件 #include <stdio.h> 双引号:自定义库文件 尖括号:操作系统下规定的库文件 /this is a helloword program/ <---注释 #include <stdio.h> <---编译预处......

吴锦涛
2012/12/09
0
2
你为什么看不懂Linux内核驱动源码?

学习嵌入式Linux驱动开发,最核心的技能就是能够编写Linux内核驱动、深入理解Linux内核。而做到这一步的基础,就是你要看得懂Linux内核源码,了解其基本的框架和具体实现,了解其内核API的使...

宅学部落
2018/04/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

nproc systemd on CentOS 7

Increasing nproc for processes launched by systemd on CentOS 7 Ask Question I have successfully increased the nofile and nproc value for the local users, but I couldn't find a p......

MtrS
25分钟前
1
0
了解微信小程序下拉刷新功能

小程序提供了这个事件。 onPullDownRefresh() 监听用户下拉刷新事件。 如果要开启下拉刷新功能,要先到json配置: "enablePullDownRefresh":true 配置后下拉有反应了但是没有加载效果,在onP...

oixan__
50分钟前
2
0
springmvc java对象转json,上传下载(未完)拦截器Interceptor以及源码解析(未完待续)

package com.atguigu.my.controller;import java.util.Collection;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Contr......

architect刘源源
今天
29
0
[日更-2019.5.24、25、26] Android系统中的Binder通信机制分析(一)--servicemanager

声明 其实对于Android系统Binder通信的机制早就有分析的想法,记得去年6、7月份Mr.Deng离职期间约定一起对其进行研究的,但因为我个人问题没能实施这个计划,留下些许遗憾... 最近,刚好在做...

Captain_小馬佩德罗
昨天
24
0
聊聊dubbo的DataStore

序 本文主要研究一下dubbo的DataStore DataStore dubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/store/DataStore.java @SPI("simple")public interface DataStore { ......

go4it
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部