
一.JNI與NDK的關(guān)系
1.什么是JNI?
JNI(Java Native Interface,Java本地接口),用于打通Java層與Native層。 這不是Android系統(tǒng)所獨(dú)有的,而是Java所有。
??Java語(yǔ)言是跨平臺(tái)的語(yǔ)言,而這跨平臺(tái)的背后都是依靠Java虛擬機(jī),虛擬機(jī)采用C/C++編寫(xiě),適配各個(gè)系統(tǒng),通過(guò)JNI為上層Java提供各種服務(wù),保證跨平臺(tái)性。通俗地說(shuō),JNI是一種技術(shù),通過(guò)這種技術(shù)可以做到以下兩點(diǎn): Java程序中的函數(shù)可以調(diào)用Native語(yǔ)言寫(xiě)的函數(shù); Native程序中的函數(shù)可以調(diào)用Java層的函數(shù)。
1.1.1 正常情況下的Android框架
最頂層是 Android的應(yīng)用程序代碼, 是純Java代碼, 中間有一層的Framework框架層, 通過(guò)Framework進(jìn)行系統(tǒng)調(diào)用底層的庫(kù) 和 linux 內(nèi)核;

1.1.2.使用JNI時(shí)的Android框架
繞過(guò)Framework提供的調(diào)用底層的代碼, 直接調(diào)用自己寫(xiě)的C++代碼,該代碼最終會(huì)編譯成為一個(gè)動(dòng)態(tài)的“.so庫(kù)(第二張圖的Native Libs)”,該動(dòng)態(tài)庫(kù)可以通過(guò)NDK提供的函數(shù)等工具,調(diào)用底下的C層Native Lib(上圖第三層)

2.什么是NDK?
NDK(英語(yǔ):native development kit,原生開(kāi)發(fā)工具包),是一種基于原生程序接口的軟件開(kāi)發(fā)工具。通過(guò)此工具開(kāi)發(fā)的程序直接以本地語(yǔ)言運(yùn)行,而非虛擬機(jī)。因此只有java等基于虛擬機(jī)運(yùn)行的語(yǔ)言的程序才會(huì)有原生開(kāi)發(fā)工具包。
??上面我們說(shuō)過(guò),JNI是Java的一種特性,因此即便沒(méi)有NDK,我們?nèi)稳豢梢杂肅艸來(lái)寫(xiě)我們的應(yīng)用,那么為什么還要NDK呢?因?yàn)樵诖酥埃贏ndroid SDK文檔里,找不到任何JNI方面的幫助。即使第三方應(yīng)用開(kāi)發(fā)者使用JNI完成了自己的C++動(dòng)態(tài)鏈接庫(kù)開(kāi)發(fā),但是so如何和應(yīng)用程序一起打包成apk并發(fā)布?這里面也存在技術(shù)障礙。
- 因此,NDK提供了一系列的工具,幫助開(kāi)發(fā)者快速開(kāi)發(fā)C(或C++)的動(dòng)態(tài)庫(kù),并能自動(dòng)將so和java應(yīng)用一起打包成apk。這些工具對(duì)開(kāi)發(fā)者的幫助是巨大的。
- NDK集成了交叉編譯器,并提供了相應(yīng)的mk文件隔離CPU、平臺(tái)、ABI等差異,開(kāi)發(fā)人員只需要簡(jiǎn)單修改mk文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創(chuàng)建出so。
二.JNI函數(shù)的動(dòng)態(tài)注冊(cè)方式
1.JNI_OnLoad
在JNI中有一組特殊的函數(shù):
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved);
這一組函數(shù)的作用就是負(fù)責(zé)Java方法和本地C函數(shù)的鏈接,其中JNI_OnLoad方法是在動(dòng)態(tài)庫(kù)被加載時(shí)調(diào)用(調(diào)用“System.loadLibrary("so庫(kù)名字");的時(shí)候”),而JNI_OnUnload則是在本地庫(kù)被卸載時(shí)調(diào)用。所以這兩個(gè)函數(shù)就是一個(gè)本地庫(kù)最重要的兩個(gè)生命周期方法。
2.JNIEnv和JavaVM的區(qū)別
JNI_OnLoad方法在動(dòng)態(tài)注冊(cè)時(shí)的全部代碼如下:
jint JNI_OnLoad(JavaVM* vm, void* resered){
JNIEnv* env = NULL;
if((*vm).GetEnv((void**) &env, JNI_VERSION_1_6)!=JNI_OK){
return JNI_ERR;
}
if(!registerNatives(env)){
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
注意到其中兩個(gè)類:JavaVM 和 JNIEnv
2.2.1 JavaVM 和 JNIEnv
JavaVM代表java的虛擬機(jī)。在java里,每一個(gè)process可以產(chǎn)生多個(gè)java vm對(duì)象,但是在android上,每一個(gè)process只有一個(gè)Dalvik虛擬機(jī)對(duì)象,也就是在android進(jìn)程中是通過(guò)有且只有一個(gè)虛擬器對(duì)象來(lái)服務(wù)所有java和c/c++代碼,這個(gè)對(duì)象是線程共享的。
??JNIEnv(JNI Interface Pointer)是提供JNI Native函數(shù)的基礎(chǔ)環(huán)境,線程相關(guān),不同線程的JNIEnv相互獨(dú)立。

從上圖可知,JNIEnv實(shí)際上就是提供了一些JNI系統(tǒng)函數(shù)。通過(guò)這些函數(shù)可以做到:
- 調(diào)用Java的函數(shù)。
- 操作jobject對(duì)象等很多事情。
如果說(shuō)到這里還不明白的話,沒(méi)關(guān)系,我們之后會(huì)講JNIEnv這個(gè)指針的具體用法。
2.2.2 Java和Android中JavaVM對(duì)象有區(qū)別
在java里,每一個(gè)process可以產(chǎn)生多個(gè)java vm對(duì)象,但是在android上,每一個(gè)process只有一個(gè)Dalvik虛擬機(jī)對(duì)象,也就是在android進(jìn)程中是通過(guò)有且只有一個(gè)虛擬器對(duì)象來(lái)服務(wù)所有java和c/c++代碼。
Android的dex字節(jié)碼和c/c++的.so同時(shí)運(yùn)行Dalvik虛擬機(jī)之內(nèi),共同使用一個(gè)進(jìn)程空間。之所以可以相互調(diào)用,也是因?yàn)橛蠨alvik虛擬機(jī)*,Dalvik虛擬機(jī)說(shuō)白了也是一個(gè)Android定制版的JVM,谷歌對(duì)他做了很多優(yōu)化和調(diào)整(比如將JVM的機(jī)制堆棧尋址改為了基于寄存器),因此它也是用C實(shí)現(xiàn)的,它的底層調(diào)的是第一張圖的第三層(系統(tǒng) Libs)。
當(dāng)java代碼需要c/c++代碼時(shí),在Dalvik虛擬機(jī)加載進(jìn).so庫(kù)時(shí),會(huì)先調(diào)用JNI_Onload(),此時(shí)就會(huì) 把JAVA VM對(duì)象的指針存儲(chǔ)于c層jni組件的全局環(huán)境中,在Java層調(diào)用C層的本地函數(shù)時(shí),調(diào)用c本地函數(shù)的線程必然通過(guò)Dalvik虛擬機(jī)來(lái)調(diào)用c層的本地函數(shù),此時(shí),Dalvik虛擬機(jī)會(huì)為本地的C組件實(shí)例化一個(gè)JNIEnv指針,該指針指向Dalvik虛擬機(jī)的具體的函數(shù)列表
當(dāng)JNI的c組件調(diào)用Java層的方法或者屬性時(shí),也需要通過(guò)JNIEnv指針來(lái)進(jìn)行調(diào)用。當(dāng)本地c/c++想獲得當(dāng)前線程所要使用的JNIEnv時(shí),可以使用Dalvik虛擬機(jī)對(duì)象的JavaVM jvm->GetEnv()返回當(dāng)前線程所在的JNIEnv*(上面代碼也展示了):
if((*vm).GetEnv((void**) &env, JNI_VERSION_1_6)!=JNI_OK){
其中JNI_VERSION_1_6為JNI版本,這個(gè)值可以通過(guò)jint GetVersion(JNIEnv *env);來(lái)獲取當(dāng)前的JNI版本,返回值是宏定義的常量,我們可以使用獲取到的值與下列宏進(jìn)行匹配來(lái)知道當(dāng)前的版本:
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
3.動(dòng)態(tài)注冊(cè)
2.3.1 JNINativeMethod
首先我們來(lái)說(shuō)說(shuō)JNINativeMethod,就是我們動(dòng)態(tài)注冊(cè)時(shí)建立的函數(shù)映射表,將Java代碼中的native函數(shù)名和native函數(shù)對(duì)應(yīng)起來(lái):
static JNINativeMethod methods[] = {
{
"nativeBaseDataType", //Java代碼中的native函數(shù)的名字
"(SIJFDCZ)V", //方法的簽名信息,主要是參數(shù)+返回值,V表示返回類型為void
(void*)baseDataType //函數(shù)指針,指向一個(gè)native中定義的C++函數(shù)
}
}
上面建立了兩個(gè)函數(shù)對(duì)應(yīng)關(guān)系,也就是JNINativeMethod數(shù)組中的兩個(gè)結(jié)構(gòu)體,每個(gè)結(jié)構(gòu)體有三個(gè)成員,第一個(gè)為Java代碼中的native函數(shù)的名字,第三個(gè)為native中對(duì)應(yīng)被調(diào)用的函數(shù)名字,我們來(lái)看看這兩個(gè)函數(shù):
java代碼中:
public static native void nativeBaseDataType(
short s, int i, long l, float f, double d, char c, boolean b );
C++代碼中:
void baseDataType(JNIEnv* jniEnv,jobject jobj,jshort s,
jint i, jlong l, jfloat f, jdouble d, jchar c, jboolean b){
ELOG("LearnJNI.cpp --> baseDataType:%d,%f,%c,%d",i,d,c,b);
}
這個(gè)方法展示了Java層向C++層傳遞基本類型數(shù)據(jù)??梢钥吹絁NINativeMethod數(shù)組中結(jié)構(gòu)體的第一個(gè)元素和第三個(gè)元素就是這兩個(gè)地方方法的名字(其中第三個(gè)元素前面必須加上(void*))
我們?cè)賮?lái)看看結(jié)構(gòu)體中第二個(gè)元素,(SIJFDCZ)V,這就是Java代碼中對(duì)應(yīng)的JNI函數(shù)的簽名,也就是參數(shù)類型+返回值類型,只是這個(gè)類型有一定的對(duì)應(yīng)關(guān)系:

因此public static native void nativeBaseDataType(short s, int i, long l, float f, double d, char c, boolean b );對(duì)應(yīng)(SIJFDCZ)V;public static native String nativeReturnString();對(duì)應(yīng)()Ljava/lang/String;
??同時(shí)應(yīng)當(dāng)注意,jni函數(shù)簽名中。參數(shù)類型之間不用“;”隔開(kāi)(除了String類型的標(biāo)識(shí)為“ Ljava/lang/String; ”自帶“;”),直接連在一起就可以了,同時(shí)數(shù)組標(biāo)識(shí)是在元素類型前面加“[”,如int[]標(biāo)識(shí)為“[I”.
2.3.2 RegisterNatives
接著2中的那段代碼,看看“registerNatives()”:
static int registerNatives(JNIEnv *env) {
const char *className = "com/example/dell/growup/LearnJNI"; //指定注冊(cè)的類
return registerNativeMethods(env, className, methods, sizeof(methods) / sizeof(methods[0]));
}
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
int numMethods) {
jclass clazz;
clazz = (*env).FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env).RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
可以看到,這里JNIEnv env排上用場(chǎng)了,首先className中我們指定了注冊(cè)native函數(shù)的包名+類名,接著我們調(diào)用JNIEnv的FindClass*方法clazz = (*env).FindClass(className);來(lái)獲取注冊(cè)native函數(shù)的類,之后通過(guò)JNIEnv調(diào)用JNI函數(shù) (*env).RegisterNatives(clazz, gMethods, numMethods) 來(lái)注冊(cè)上面JNINativeMethod中定義的對(duì)應(yīng)函數(shù)。
當(dāng)Java層通過(guò)System.loadLibrary加載完JNI動(dòng)態(tài)庫(kù)后,緊接著會(huì)查找該庫(kù)中一個(gè)叫JNI_OnLoad的函數(shù),如果有,就調(diào)用它,而動(dòng)態(tài)注冊(cè)的工作就是在這里完成的。
??所以,如果想使用動(dòng)態(tài)注冊(cè)方法,就必須要實(shí)現(xiàn)JNI_OnLoad函數(shù),在這個(gè)函數(shù)中會(huì)完成動(dòng)態(tài)注冊(cè)的工作。靜態(tài)注冊(cè)則沒(méi)有這個(gè)要求,一般我們可以自己實(shí)現(xiàn)這個(gè)JNI_OnLoad函數(shù),并在其中做一些初始化工作(即使是靜態(tài)注冊(cè))。
我們?cè)儋N一遍JNI_OnLoad()函數(shù)代碼:
//該函數(shù)的第一個(gè)參數(shù)類型為JavaVM,是虛擬機(jī)在JNI層的代表,每個(gè)Java進(jìn)程只有一個(gè)
jint JNI_OnLoad(JavaVM* vm, void* resered){
JNIEnv* env = NULL;
if((*vm).GetEnv((void**) &env, JNI_VERSION_1_6)!=JNI_OK){
return JNI_ERR;
}
if(!registerNatives(env)){
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
三.JNIEnv的各種操作
1.基本類型數(shù)據(jù)處理
其實(shí)基本類型處理上面我們已經(jīng)展示過(guò)了,這里再貼一遍代碼:
java代碼中:
public static native void nativeBaseDataType(
short s, int i, long l, float f, double d, char c, boolean b);
C++代碼中:
void baseDataType(JNIEnv* jniEnv,jobject jobj,
jshort s,jint i, jlong l, jfloat f, jdouble d, jchar c, jboolean b){
ELOG("LearnJNI.cpp --> baseDataType:%d,%f,%c,%d",i,d,c,b);
}
可以看到,java中的基本數(shù)據(jù)類型,在native中基本上就是在前面加了個(gè)“j”,實(shí)際上也就是在“jni.h”(NDK提供的jni開(kāi)發(fā)類)中給java中的基本類型用結(jié)構(gòu)體包裝類一下:
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
從Java層往C++層傳遞基本類型數(shù)據(jù)是很容易的,只要上面函數(shù)的映射關(guān)系建立好了,就跟普通函數(shù)傳參一樣。
2.String字符串處理
這里我們展示一個(gè)從java向C++傳基本類型、String、數(shù)組,并從C++中像向Java中傳遞String的例子:
Java代碼中:
void returnString(){
int i =3;
char[] c = {'J','N','I'};
String s = "learn";
String string = nativeReturnJavaString(i,s,c);
Log.e("returnString",string);
}
public static native String nativeReturnJavaString(int i, String s, char[] c);
C++代碼:
static JNINativeMethod methods[] = {
{
"nativeReturnJavaString",
"(ILjava/lang/String;[C)Ljava/lang/String;", //注意多種數(shù)據(jù)類型簽名時(shí)中間沒(méi)有分號(hào)或逗號(hào)
(void*)returnJavaString
}
}
jstring returnJavaString(JNIEnv *jniEnv, jobject jobj, jint i, jstring j_str, jcharArray j_char){
const char* c_str = NULL;
jchar* j_charArray = NULL;
jint arr_len;
jint str_len;
char buff[120] = {0};
jboolean isCopy;
arr_len = (*jniEnv).GetArrayLength(j_char);// 獲取char數(shù)組長(zhǎng)度
str_len = (*jniEnv).GetStringLength(j_str);// 獲取String長(zhǎng)度
j_charArray = (jchar*)malloc(sizeof(jchar)* arr_len); // 根據(jù)數(shù)組長(zhǎng)度和數(shù)組元素的數(shù)據(jù)類型申請(qǐng)存放java數(shù)組元素的緩沖區(qū)
memset(j_charArray, 0,sizeof(jchar)* arr_len ); // 初始化緩沖區(qū)
(*jniEnv).GetCharArrayRegion(j_char, 0, arr_len, j_charArray); // 拷貝Java數(shù)組中的所有元素到緩沖區(qū)中
c_str = (*jniEnv).GetStringUTFChars(j_str, &isCopy);
sprintf(buff, "%s", c_str); //sprintf(s, "%d", 123); //把整數(shù)123打印成一個(gè)字符串保存在s中
for(int j=0; j<i; j++){
buff[str_len+j] = (char) j_charArray[j];
//ELOG("LearnJNI.cpp --> returnJavaString:%c",buff[str_len+j]);
}
free(j_charArray); // 釋放存儲(chǔ)數(shù)組元素的緩沖區(qū)
(*jniEnv).ReleaseStringUTFChars(j_str,c_str);
return jniEnv->NewStringUTF(buff);
}
我們知道,Java中String是一個(gè)對(duì)象,他申明的對(duì)象是存放在JVM內(nèi)部數(shù)據(jù)結(jié)構(gòu)中的(堆,棧,靜態(tài)區(qū))。 JNI把Java中的所有對(duì)象當(dāng)作一個(gè)C指針(對(duì)象的引用)傳遞到本地方法中,這個(gè)指針指向JVM中的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(即這個(gè)對(duì)象存放的地址),而內(nèi)部的數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的存儲(chǔ)方式是不可見(jiàn)的。只能從JNIEnv指針指向的函數(shù)表中選擇合適的JNI函數(shù)來(lái)操作JVM中的數(shù)據(jù)結(jié)構(gòu)。
訪問(wèn)java.lang.String對(duì)應(yīng)的JNI類型jstring時(shí),沒(méi)有像訪問(wèn)基本數(shù)據(jù)類型一樣直接使用,因?yàn)?strong>它在Java是一個(gè)引用類型,所以在本地代碼中只能通過(guò)GetStringUTFChars這樣的JNI函數(shù)來(lái)訪問(wèn)字符串的內(nèi)容。
3.2.1 const char* GetStringUTFChars(env, j_str, &isCopy)
env:JNIEnv函數(shù)表指針
j_str:jstring類型(Java傳遞給本地代碼的字符串指針)
isCopy:取值JNI_TRUE和JNI_FALSE,如果值為JNI_TRUE,表示返回JVM內(nèi)部源字符串的一份拷貝,并為新產(chǎn)生的字符串分配內(nèi)存空間。如果值為JNI_FALSE,表示返回JVM內(nèi)部源字符串的指針,意味著可以通過(guò)指針修改源字符串的內(nèi)容,不推薦這么做,因?yàn)檫@樣做就打破了Java字符串不能修改的規(guī)定。但我們?cè)陂_(kāi)發(fā)當(dāng)中,并不關(guān)心這個(gè)值是多少,通常情況下這個(gè)參數(shù)填NULL即可。
因?yàn)?strong>Java默認(rèn)使用Unicode編碼,而C/C++默認(rèn)使用UTF編碼,所以在本地代碼中操作字符串的時(shí)候,必須使用合適的JNI函數(shù)把jstring轉(zhuǎn)換成C風(fēng)格的字符串。JNI支持字符串在Unicode和UTF-8兩種編碼之間轉(zhuǎn)換,GetStringUTFChars可以把一個(gè)jstring指針(指向JVM內(nèi)部的Unicode字符序列)轉(zhuǎn)換成一個(gè)UTF-8格式的C字符串。
3.2.2 jstring NewStringUTF(const char* bytes)
通過(guò)調(diào)用NewStringUTF函數(shù),會(huì)構(gòu)建一個(gè)新的java.lang.String字符串對(duì)象。這個(gè)新創(chuàng)建的字符串會(huì)自動(dòng)轉(zhuǎn)換成Java支持的Unicode編碼。
3.2.3 void ReleaseStringUTFChars(jstring string, const char* utf)
在調(diào)用GetStringUTFChars函數(shù)從JVM內(nèi)部獲取一個(gè)字符串之后,JVM內(nèi)部會(huì)分配一塊新的內(nèi)存,用于存儲(chǔ)源字符串的拷貝,以便本地代碼訪問(wèn)和修改。即然有內(nèi)存分配,用完之后馬上釋放.通過(guò)調(diào)用ReleaseStringUTFChars函數(shù)通知JVM這塊內(nèi)存已經(jīng)不使用了,你可以清除了。注意:這兩個(gè)函數(shù)是配對(duì)使用的,用了GetXXX就必須調(diào)用ReleaseXXX,而且這兩個(gè)函數(shù)的命名也有規(guī)律,除了前面的Get和Release之外,后面的都一樣。
3.訪問(wèn)數(shù)組
??訪問(wèn)數(shù)組的例子上面已經(jīng)展示過(guò)了,這里主要說(shuō)明一下幾個(gè)訪問(wèn)數(shù)組相關(guān)的函數(shù)的用法。
3.3.1 jsize GetArrayLength(jarray array)
??獲取數(shù)組的長(zhǎng)度,返回值為jint類型,我們獲取數(shù)組的類型之后就可以調(diào)用malloc函數(shù)動(dòng)態(tài)的分配內(nèi)存:
jint arr_len;
arr_len = (*jniEnv).GetArrayLength(j_char);
jchar* j_charArray = NULL;
j_charArray = (jchar*)malloc(sizeof(jchar)* arr_len);
memset(j_charArray, 0,sizeof(jchar)* arr_len );
??我們需要一個(gè)jchar* 類型的指針指向我們剛分配的內(nèi)存(的首地址),以便之后調(diào)用 memset函數(shù)的時(shí)候找到這塊內(nèi)存并將其中的字節(jié)初始化為0(申請(qǐng)的內(nèi)存中可能有之前殘余的值),我們來(lái)看看這個(gè)memset函數(shù):
memset() 函數(shù)用來(lái)將指定內(nèi)存的前n個(gè)字節(jié)設(shè)置為特定的值(經(jīng)常用于初始化剛剛申請(qǐng)的內(nèi)存),其原型為:
void * memset( void * ptr, int value, size_t num );
參數(shù)說(shuō)明:
ptr 為要操作的內(nèi)存的指針。
value 為要設(shè)置的值。你既可以向 value 傳遞 int 類型的值,也可以傳遞 char 類型的值,
int 和 char 可以根據(jù) ASCII 碼相互轉(zhuǎn)換。
num 為 ptr 的前 num 個(gè)字節(jié),size_t 就是unsigned int。
3.3.2 void GetCharArrayRegion(jcharArray array, jsize start, jsize len,jchar* buf)
...
j_charArray = (jchar*)malloc(sizeof(jchar)* arr_len);
memset(j_charArray, 0,sizeof(jchar)* arr_len ); // 初始化緩沖區(qū)
(*jniEnv).GetCharArrayRegion(j_char, 0, arr_len, j_charArray); // 拷貝Java數(shù)組中的所有元素到緩沖區(qū)中
c_str = (*jniEnv).GetStringUTFChars(j_str, &isCopy);
sprintf(buff, "%s", c_str); //sprintf(s, "%d", 123); //把整數(shù)123打印成一個(gè)字符串保存在s中
??GetCharArrayRegion將Java數(shù)組j_char,拷貝到我們剛剛申請(qǐng)的內(nèi)存j_charArray中,之后我們調(diào)用c_str = (*jniEnv).GetStringUTFChars(j_str, &isCopy);將java層傳來(lái)的 j_str(String類型)轉(zhuǎn)換成一個(gè)C風(fēng)格字符串(c_str),然后sprintf(buff, "%s", c_str);是將作為C風(fēng)格的字符串儲(chǔ)存在buff數(shù)組中。