C語言基礎及指標⑩預編譯及jni.h分析
接續上篇C語言基礎及指標⑨聯合體與列舉
在上篇中我們瞭解了 , 多型別集合的聯合體 , 固定值集合的列舉 , 內容相對比較簡單 , 今天我們談談預編譯 , 也是本系列最後一個知識點 , C語言基礎系列就要告一段落了 , 要開始我們的jni系列了 , JNI(Java Native Interface) 是java與C/C++進行通訊的一種技術 , 使用JNI技術,可以java呼叫C/C++的函式物件等等,Android中的Framework層與Native層就是採用的JNI技術 。
預編譯
預編譯(預處理,巨集定義,巨集替換)這種叫法 , 關鍵字
#define
, 其本質是替換文字。
首先我們瞭解一下C語言的執行過程:
- 編譯 --> 生成目的碼(.obj)
- 連線 --> 將目的碼與C函式庫合併 , 生成最終可執行檔案
- 執行
預編譯 , 主要在編譯時期完成文字替換工作 , 常見的預編譯指令有:
#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:我是錯誤日誌...
我們可以看到輸出資訊前面帶有型別標識 。在這裡做幾點程式碼說明:
- 上述程式碼中
LEVE
,FORMAT
都是自定義的 , 可以是任意名稱 , 只有後面替換的名稱一致即可。
LOG(LEVE,FORMAT,...)
中的...
表示可變引數 , 替換則是使用__VA_ARGS__
這種固定寫法 。- 巨集函式的核心就是——替換
預編譯指令就介紹到這裡 , 其中心點就是替換
。 學到這裡 , 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*);
- 將字元指標轉換成java的String型別
- 得到java的String型別長度
- 將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結合起來 , 著手做些東西出來 , 然後再繼續深入 。
語言都是相通的 , 關鍵是解決問題的思路 。