Android Ndk(Beginner ‘s guide)(3.1)

使用JNI连接java和C/C++

(我就懂java的基本语法,所以后面很多翻译可能有错 )
在本章我们将学习以下东西:

1.在java与本地代码之间传递与接收基本类型、对象、数组

2.在本地代码里面处理java对象

3.从本地代码中引起异常

    Jni是一项巨大的高度技术性的科目,如果要深入研究需要一整本书来详细讲解。然而我们在这章将集中了解在他是怎么连接
java和c++的。

与java基本类型的联系

你可能已经迫不及待的想学更多的东西,而不是前面几章里面简单的MyProject:传递参数,接收返回值,产生异常,追踪客体目标。通过这章我们将会完成不同数据类型的值的存储,以基本数据类型和字符串开始。
    一个简单的Java GUI是由一个关键字(一个特殊的字符串),类型(整形,字符串等等)和一个与所选类型有关的值所定义的接口。接口被插入或者修改的数据将会驻留在本地代码里面。接口也可以被Java客户端回调。下面的图片展示了一个程序大概会被怎样构架:


动手时间——建立一个本地值/关键字Store

    首先实现Java方面的代码:

    1.建立一个Java/C++的混合项目

命名为Store
主包为com.packtpub
主Activity为StoreActivity
不要忘了在根目录下面创建Jni文件夹
java方面将会包含三个源文件 Store.java    StoreType.java  和 StoreActivity.java

    2.建立一个新的Store类导入与本地library同名的库,定义我们store将要听过的函数。Store是我们本地代码 的前端。它仅仅支持整型和字符串型

public class Store {
		static {
			System.loadLibrary(“store”);
		}
		public native int getInteger(String pKey);
		public native void setInteger(String pKey, int pInt);
		public native String getString(String pKey);
		public native void setString(String pKey, String pString);
}

3.以enum建立StoreType.java支持特定的几种数据类型

public enum StoreType {
	Integer, String
}

4.在res/layout/main.xml文件里面按照下面的图片建立Java GUI。你可以是用ADT图形输出来设计,或者更简单的从Store_part3-1里面直接复制过去。


5.应用程序的GUI和Store需要被绑定在一起。这就是StoreActivity类将要实现的。当activity被创建,创建GUI的元素:类型容器就会与StoreType枚举类绑定在一起。GetValue和SetValue按钮就会触发私有函数onGetValue()和onSetValue()最后,初始化一个新的store的对象:

public class StoreActivity extends Activity {
	private EditText mUIKeyEdit, mUIValueEdit;
	private Spinner mUITypeSpinner;
	private Button mUIGetButton, mUISetButton;
	private Store mStore;
@Override
	public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
// Initializes components and binds buttons to handlers.
	...
	mStore = new Store();
}

6.定义onGetValue()函数,根据GUI里面选择的StoreType类型获得一个返回值

private void onGetValue() {
	String lKey = mUIKeyEdit.getText().toString();
	StoreType lType = (StoreType) mUITypeSpinne.getSelectedItem();
	switch (lType) {
	case Integer:
		mUIValueEdit.setText(Integer.toString(mStore.getInteger(lKey)));
		break;
	case String:
		mUIValueEdit.setText(mStore.getString(lKey));
		break;
	}
}

7.在StoreActivity里面添加onSetValue()函数来插入或者更新一个值。Entry的值需要根据他的类型来被描述
如果值的格式不正确,将会有一个提示信息显示:

...
private void onSetValue() {
	String lKey = mUIKeyEdit.getText().toString();
	String lValue = mUIValueEdit.getText().toString();
	StoreType lType = (StoreType) mUITypeSpinner.getSelectedItem();
	try {
		switch (lType) {
			case Integer:
				mStore.setInteger(lKey, Integer.parseInt(lValue));
				break;
			case String:
				mStore.setString(lKey, lValue);
				break;
				}
	} catch (NumberFormatException eNumberFormatException) {
		displayError(“Incorrect value.”);
		}
		}
private void displayError(String pError) {
		Toast.makeText(getApplicationContext(), pError,
		Toast.LENGTH_LONG).show();
	}
}

Java方面的代码已经完成,也定义了本地方法的原型,我们将写本地代码。你可以查看最终代码获取帮助
8.在Jni目录下面,创建定义了数据结构体的Store.h。创建一个StoreType枚举出匹配java方面的类型。
同时创建Store。StoreEntry包含了一个关键字(C字符串),类型和一个值。StoreValue就是一个包含所有可能值
的集合体:

#ifndef _STORE_H_
#define _STORE_H_
#include “jni.h”
#include <stdint.h>
#define STORE_MAX_CAPACITY 16
typedef enum {
	StoreType_Integer, StoreType_String
} StoreType;
typedef union {
	int32_t mInteger;
	char* mString;
} StoreValue;
typedef struct {
	char* mKey;
	StoreType mType;
	StoreValue mValue;
} StoreEntry;
typedef struct {
	StoreEntry mEntries[STORE_MAX_CAPACITY];
	int32_t mLength;
} Store;
...

9.最后声明一个函数来创建,找到,回收entry来结束Store.h。JNIEnv和jstring类型已经在jni.h的头文件里面定义了

...
int32_t isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry,StoreType pType);
StoreEntry* allocateEntry(JNIEnv* pEnv, Store* pStore, jstring pKey);
StoreEntry* findEntry(JNIEnv* pEnv, Store* pStore, jstring pKey,int32_t* pError);
void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry);
所有这些函数都在jni/Store.c里面,isEntryValid()仅仅检查一个entry是否被分配以及类型是否正确:
#include “Store.h”
#include <string.h>
int32_t isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry,StoreType pType) {
	if ((pEntry != NULL) && (pEntry->mType == pType)) {
	return 1;
}
return 0;
}
...

10.findEntry()函数用来比较所有传递进来的值,直到找到一个正确的。他接收的是一个jstring格式的参数,这个参数是java用来代替C代码对应格式的string,而不是标准的C语言string格式。jstring不能直接在本地代码中使用。事实上,Java和C的string是完全不同的两种概念。在Java中,String是一个有着成员方法的实体对象,而在C中,string只是一组字符数组。
为了从Java String中获取对应的C string,可以使用JNI API函数 GetStringUTFChars()来获取一个零时字符缓存。然后就能使用标准的C流程来处理。使用了GetStringUTFChars()就必须使用对应的ReleaseStringUTFChars()来释放零时的缓冲空间:

StoreEntry* findEntry(JNIEnv* pEnv, Store* pStore, jstring pKey,Int32_t* pError) {
	StoreEntry* lEntry = pStore->mEntries;
	StoreEntry* lEntryEnd = lEntry + pStore->mLength;
	const char* lKeyTmp = (*pEnv)->GetStringUTFChars(pEnv, pKey,NULL);
	if (lKeyTmp == NULL) {
		if (pError != NULL) {
			*pError = 1;
			}
		return;
	}
	while ((lEntry < lEntryEnd)&& (strcmp(lEntry->mKey, lKeyTmp) != 0)) {
		++lEntry;
	}
	(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
return (lEntry == lEntryEnd) ? NULL : lEntry;
}
...

11.在Store.c里面,实现allocateEntry()函数,它既能用来创建一个新的entry,当值存在的时候也能用来返回一个已经存在的。如果entry是新的,在函数外面的内存里面转换成C的格式

StoreEntry* allocateEntry(JNIEnv* pEnv, Store* pStore, jstring pKey)
{
	Int32_t lError = 0;
	StoreEntry* lEntry = findEntry(pEnv, pStore, pKey, &lError);
	if (lEntry != NULL) {
		releaseEntryValue(pEnv, lEntry);
	} else if (!lError) {
	if (pStore->mLength >= STORE_MAX_CAPACITY) {
		return NULL;
	}
	lEntry = pStore->mEntries + pStore->mLength;
	const char* lKeyTmp = (*pEnv)->GetStringUTFChars (pEnv, pKey, NULL);
	if (lKeyTmp == NULL) {
		return;
			}
	lEntry->mKey = (char*) malloc(strlen(lKeyTmp));
	strcpy(lEntry->mKey, lKeyTmp);
	(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
	++pStore->mLength;
	}
		return lEntry;
}
...

12.Store.c代码中最后一个函数是releaseEntryValue(),用来释放为一个值分配的内存空间。目前只有string是被动态分配和需要被回收的

void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry) {
int i;
switch (pEntry->mType) {
	case StoreType_String:
	free(pEntry->mValue.mString);
	break;
		}
}
#endif

13.使用javah像前面第2章里面为com.packtpub.Store生成JNI的头文件。jni/com_packtpub_Store.h应该被生成
14.现在我们的功能函数和JNI头文件已经生成了。我们需要写JNI的资源文件。com_packtpub_Store.c。唯一的Store线程被保存在一个静态的变量里面当library被装载的时候才创建。

#include “com_packtpub_Store.h”
#include “Store.h”
#include <stdint.h>
#include <string.h>
static Store gStore = { {}, 0 };

15.在生成的JNI头文件的帮助下,完成getInteger()和setInteger()函数(在com_packtpub_Store.c里面)
第一个函数用来在store里面查找已经确认过的关键字,并返回其对应的值。如果发生什么异常,就会返回一个默认值。
第二个函数用来分配一个新的entry并保存新的integer值在里面(新建一个或者重新只用已有的)。注意这里C的int类型可以直接转换成Java的jint,反过来也一样。事实上他们是一样的类型:

JNIEXPORT jint JNICALL Java_com_packtpub_Store_getInteger(JNIEnv* pEnv, jobject pThis, jstring pKey) {
	StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
		if (isEntryValid(pEnv, lEntry, StoreType_Integer)) {
			return lEntry->mValue.mInteger;
		} else {
			return 0.0f;
			}
}
JNIEXPORT void JNICALL Java_com_packtpub_Store_setInteger(JNIEnv* pEnv, jobject pThis, jstring pKey, jint pInteger) {
		StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
			if (lEntry != NULL) {
				lEntry->mType = StoreType_Integer;
				lEntry->mValue.mInteger = pInteger;
			}
}

16.处理String的时候需要更加留心。Java的string并不是真正的原始数据,在第11步里面的jstring和char *是不能互换的。
使用NewStringUTF()创建Java String. 第2个函数setString()用GetStringUTFChars()和SetStringUTFChars()把Java String转换成C string

JNIEXPORT jstring JNICALL Java_com_packtpub_Store_getString(JNIEnv* pEnv, jobject pThis, jstring pKey) {
	StoreEntry* lEntry = findEntry(pEnv, &gStore, pKey, NULL);
		if (isEntryValid(pEnv, lEntry, StoreType_String)) {
			return (*pEnv)->NewStringUTF(pEnv, lEntry->mValue.mString);
		}
		else {
			return NULL;
		}
}
JNIEXPORT void JNICALL Java_com_packtpub_Store_setString(JNIEnv* pEnv, jobject pThis, jstring pKey, jstring pString) {
	const char* lStringTmp = (*pEnv)->GetStringUTFChars(pEnv,pString, NULL);
		if (lStringTmp == NULL) {
			return;
		}
	StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
		if (lEntry != NULL) {
			lEntry->mType = StoreType_String;
			jsize lStringLength = (*pEnv)->GetStringUTFLength(pEnv,pString);
			lEntry->mValue.mString =(char*) malloc(sizeof(char) * (lStringLength + 1));
			strcpy(lEntry->mValue.mString, lStringTmp);
		}
	(*pEnv)->ReleaseStringUTFChars(pEnv, pString, lStringTmp);
}

17.最后按照下面的格式写Android.mk。库名为store.以及列出的两个C文件。在项目的根目录使用ndk-build来编译C代码:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS := -DHAVE_INTTYPES_H
LOCAL_MODULE := store
LOCAL_SRC_FILES := com_packtpub_Store.c Store.c
include $(BUILD_SHARED_LIBRARY)

刚刚完成了什么?

运行程序,使用不同的值以及类型保存值。然后从本地方法里面取得输入的值。我们已经完成了C和Java之间的传递与接收int类型的值。这些值被store以string类型保存着。输入的值可以根据他们的值或者类型取回。整型在native调用之间有几种不同的状态,首先在Java里面是int,然后在传进/传出java代码的时候是jint格式。最后在本地代码中是int/int32_t。显然我们可以保存JNI的代表类型jint在本地代码里面因为两种类型是相等的。更普遍的,原始数据型不同语言只有有对应代替


另一方面,Java string需要一个固定的转换方法转换到C string这样可以允许使用标准C方法来处理字符串。事实上jstring并不是一个典型的char *数组仅仅是一个Java String对象的引用。仅仅只能在java方使用。
转换是由JNI方法GetStringUTFChars()实现的同时必须调用ReleaseStringUTFChars()来释放。本质上,这个转转分配了一个新的字符串缓冲区。结果的C string是用UTF-8编码的允许使用标准C处理。改良的UTF-8可以代表标准的ASCII字符串,并且可以延伸成几种扩展类型。这种格式与java的UTF-16是不同的。在获取本地strings的时候为了避免内部的转换,JNI同样也提供了GetStringChars()和ReleaseStringCharset(),返回UTF-16。与GetStringLength()连接的时候强烈建议使用它(因为GetStringUTFLength()可以使用标准函数strlen()(改良的UTF-8格式)来代替)









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值