接續(xù)上篇C語言基礎(chǔ)及指針⑨聯(lián)合體與枚舉
在上篇中我們了解了 , 多類型集合的聯(lián)合體 , 固定值集合的枚舉 , 內(nèi)容相對比較簡單 , 今天我們談?wù)勵(lì)A(yù)編譯 , 也是本系列最后一個(gè)知識(shí)點(diǎn) , C語言基礎(chǔ)系列就要告一段落了 , 要開始我們的jni系列了 , JNI(Java Native Interface) 是java與C/C++進(jìn)行通信的一種技術(shù) , 使用JNI技術(shù),可以java調(diào)用C/C++的函數(shù)對象等等,Android中的Framework層與Native層就是采用的JNI技術(shù) 。
預(yù)編譯
預(yù)編譯(預(yù)處理,宏定義,宏替換)這種叫法 , 關(guān)鍵字
#define
, 其本質(zhì)是替換文本。
首先我們了解一下C語言的執(zhí)行過程:
- 編譯 --> 生成目標(biāo)代碼(.obj)
- 連接 --> 將目標(biāo)代碼與C函數(shù)庫合并 , 生成最終可執(zhí)行文件
- 執(zhí)行
預(yù)編譯 , 主要在編譯時(shí)期完成文本替換工作 , 常見的預(yù)編譯指令有:
#include
,ifndef
,#endif
,define
,#pragma once
等等 。
我們在jni.h頭文件中 , 可以看到較多的預(yù)編譯指令 , 例如:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
如果編譯環(huán)境是C++, 則使用:
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
C語言編譯環(huán)境 , 則使用:
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
這里的定義就是我們后需要結(jié)束的JNIEnv
指針 , 在C++環(huán)境中JNIEnv
是一個(gè)一級(jí)指針 , 但是在C語言環(huán)境中 , 他是一個(gè)二級(jí)指針 ,這個(gè)我們將在jni系列中 , 再詳細(xì)說明 。
預(yù)編譯示例
// 定義一個(gè)常數(shù)
#define MAX 100
void main() {
int i = 99;
if (i < MAX) // 在編譯時(shí)期, 會(huì)將MAX替換成100
{
printf("i 小于 MAX\n");
}
}
定義一個(gè)宏常量MAX
他的代表100
, 宏常數(shù)沒有類型 , 只做替換。
宏函數(shù)
#define
預(yù)編譯指令 , 不光可以定義常量 , 還可以定義函數(shù) , 因?yàn)槠浔举|(zhì)是替換 , 所有可以簡化很多很長的函數(shù)名稱 。
在jni.h中 , 也可以看到宏函數(shù) , 如下:
#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
都是替換標(biāo)識(shí) , 替換_jtype
,##_jname##
。
宏函數(shù)示例
// 普通函數(shù)
void _jni_define_func_read() {
printf("read\n");
}
void _jni_define_func_write() {
printf("write\n");
}
// 定義宏函數(shù)
#define jni(NAME) _jni_define_func_##NAME() ;
void main() {
// 宏函數(shù)
//jni(read); 可以簡化函數(shù)名稱
jni(write) ;
system("pause");
}
宏函數(shù)的核心就是替換 , 只要記住這一點(diǎn)就夠了 。下面我們就來做另一個(gè)實(shí)例 , 打印類似android Log日志的形式的函數(shù)。
// 模擬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", "我是錯(cuò)誤日志...\n");
system("pause");
}
輸出:
INFO:自定義日志。。。。
ERROR:我是錯(cuò)誤日志...
我們可以看到輸出信息前面帶有類型標(biāo)識(shí) 。在這里做幾點(diǎn)代碼說明:
- 上述代碼中
LEVE
,FORMAT
都是自定義的 , 可以是任意名稱 , 只有后面替換的名稱一致即可。
-
LOG(LEVE,FORMAT,...)
中的...
表示可變參數(shù) , 替換則是使用__VA_ARGS__
這種固定寫法 。 - 宏函數(shù)的核心就是——替換
預(yù)編譯指令就介紹到這里 , 其中心點(diǎn)就是替換
。 學(xué)到這里 , C語言的基礎(chǔ)也介紹得七七八八了 , 下面一起來分析分析 , 我們編寫JNI代碼的一個(gè)比較重要的頭文件jni.h 。
簡要分析jni.h
jni.h我們編寫NDK的時(shí)候需要引入的一個(gè)頭文件 , 它位于android-ndk-r10e\platforms\android-21\arch-arm\usr\include\jni.h
不同的平臺(tái)都有相應(yīng)的jni.h 。
在文件開始部分 , 定義了很多類型別名 , 區(qū)分C++與C的編譯環(huán)境 , 如下:
#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;
接著定義了對象引用的枚舉 , 和方法簽名的結(jié)構(gòu)體:
typedef enum jobjectRefType {
JNIInvalidRefType = 0,
JNILocalRefType = 1,
JNIGlobalRefType = 2,
JNIWeakGlobalRefType = 3
} jobjectRefType;
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
接下來就是最重要的JNIEnv了 , 區(qū)分了C和C++環(huán)境
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是一個(gè)結(jié)構(gòu)體指針 , 里面定義了很多函數(shù) , 有調(diào)用java方法的函數(shù) , 轉(zhuǎn)換java類型的函數(shù) , 我可以看到 , C編譯環(huán)境中 , JNIEnv是struct JNINativeInterface*
的指針別名,我們來看看JNINativeInterface
結(jié)構(gòu)體是怎樣的:
/*
* 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
這個(gè)結(jié)構(gòu)體里面,定義了很多操作函數(shù) , 比較常見的字符處理函數(shù):
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*);
- 將字符指針轉(zhuǎn)換成java的String類型
- 得到j(luò)ava的String類型長度
- 將java的String類型轉(zhuǎn)換成C的字符指針
值得注意的是 , C++的JNIEnv結(jié)構(gòu)體指針 , 并沒有重新實(shí)現(xiàn) , 而生將C中的JNINativeInterface
結(jié)構(gòu)體 , 重新打了一次包 , 如下:
/*
* 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); }
因?yàn)镃++是面向?qū)ο笳Z言 , 所有有上下文環(huán)境變量this
, 簡化了C語言中調(diào)用函數(shù)需要傳遞本身結(jié)構(gòu)體指針的操作 。
分析了部分jni.h的源碼 , 后續(xù)的源碼 , 我相信看完C語言系列, 也可以看得懂 , 這里就不再做分析了 , 讀者可自行查看源碼分析一遍 ,對寫jni代碼還是有好處的 , 至少對JNIEnv結(jié)構(gòu)體指針中的函數(shù)有一個(gè)大致的印象 。
結(jié)語
C語言基礎(chǔ)系列 , 就宣告完結(jié)了 , 還有很多知識(shí)點(diǎn)沒講到 , 還有很多需要學(xué)習(xí) , 但我們不是要把C語言學(xué)透了學(xué)精了 , 再去學(xué)JNI 和NDK, 這樣 , 不知要到何年何月了 。 所以 , 先學(xué)基礎(chǔ)了解概貌 , 將java與C結(jié)合起來 , 著手做些東西出來 , 然后再繼續(xù)深入 。
語言都是相通的 , 關(guān)鍵是解決問題的思路 。
本文由老司機(jī)學(xué)院(動(dòng)腦學(xué)院)特約提供
做一家受人尊敬的企業(yè),做一位受人尊敬的老師
Android程序員學(xué)C系列:
C語言基礎(chǔ)及指針①
C語言基礎(chǔ)及指針②之指針內(nèi)存分析
C語言基礎(chǔ)及指針③函數(shù)與二級(jí)指針
C語言基礎(chǔ)及指針④函數(shù)指針
C語言基礎(chǔ)及指針⑤動(dòng)態(tài)內(nèi)存分配
C語言基礎(chǔ)及指針⑥字符操作
C語言基礎(chǔ)及指針⑦結(jié)構(gòu)體與指針
C語言基礎(chǔ)及指針⑧文件IO
C語言基礎(chǔ)及指針⑨聯(lián)合體與枚舉
C語言基礎(chǔ)及指針⑩預(yù)編譯及jni.h分析