JNI Basic for NDK -3

C++ 2012. 12. 27. 22:37 |

JNI Basic for NDK - 3

배열 다루기

JNI에서는 기본형 배열과 오브젝트 배열을 다르게 취급합니다. 기본형 배열이란 int나 boolean 같은 기본형으로 이루어진 배열을 말합니다. 오브젝트 배열이란 클래스의 인스턴스나 다른 배열을 가지고 있는 배열을 말합니다. 아래 코드를 볼까요.

int[] iarr;

float[] farr;

Object[] oarr;

int[][] arr2;

여기서 iarr과 farr은 기본형 배열입니다. oarr과 arr2는 오브젝트 배열이죠.

1. 기본형 배열

네이티브 메써드에서 기본형 배열을 다루는 것은 앞서 본 문자열을 다루는 것과 비슷합니다.

이클립스에서 새 Android Project를 생성합니다. New Android Project 창에 아래와 같이 입력하고 Finish를 클릭합니다.

Project Name : IntArray
Build Target : Android 2.2
Application name : IntArray
Package name : ndk.intarray

자동 생성된 IntArrayActivity.java의 IntArrayActivity class의 내용을 위의 코드와 같이 수정합니다.

public class IntArrayActivity extends Activity {
private native int sumArray(int[] arr);

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

int arr[] = new int[10];
for(int i = 0; i < 10; i++) {
arr[i] = i;
}
int sum = sumArray(arr);
String str = new String("sum = " + sum);

TextView tv = new TextView(this);
tv.setText(str);
setContentView(tv);
}

static {
System.loadLibrary("IntArray");
}
}

arr이라고 하는 배열에 담겨 있는 숫자를 네이티브 메써드인 sumArray를 사용해서 더하는 간단한 프로그램입니다.

1.1 C에서 배열에 접근하기

javah를 사용해서 네이티브 메써드인 sumArray의 C 함수 원형을 알아보면 아래와 같습니다.

JNIEXPORT jint JNICALL Java_ndk_intarray_IntArrayActivity_sumArray
(JNIEnv *, jobject, jintArray);

여기서 보듯이 int 형의 배열은 JNI에서는 jintArray 형으로 표현이 됩니다. 앞에서 본 jstring과 마찬가지로 jintArray도 C 언어에서의 배열과는 다릅니다. 잘 못 생각하고 아래와 같이 구현을 하면 이상한 결과가 나올 겁니다.

JNIEXPORT jint JNICALL Java_ndk_intarray_IntArrayActivity_sumArray
(JNIEnv *env, jobject obj, jintArray arr)

{

jint i, sum = 0;

for (i=0; i < 10; i++) {

sum += arr[i];

}

return sum;

}

기본형 배열을 다루기 위해서는 반드시 JNI 함수를 사용해야합니다. 아래의 코드는 적당한 JNI 함수를 사용해서 새로 구현한 네이티브 메써드입니다.

JNIEXPORT jint JNICALL Java_ndk_intarray_IntArrayActivity_sumArray
(JNIEnv *env, jobject obj, jintArray arr)
{
jint buf[10];
jint i, sum = 0;
(*env)->GetIntArrayRegion(env, arr, 0, 10, buf);
for (i = 0; i < 10; i++) {
sum += buf[i];
}
return sum;
}

1.2 기본형 배열 접근하기

위의 예에서는 배열에 접근하기 위해서 GetIntArrayRegion이라고 하는 JNI 함수를 사용했습니다. 이 함수를 사용해서 arr에 있는 모든 배열 요소를 buf에 복사를 했습니다. 세번째 인자인 0은 복사할 배열의 시작 위치를 네번째 인자인 10은 복사할 갯수를 나타냅니다. 이 함수에 대응하는 JNI 함수인 SetIntArrayRegion은 C 배열에 있는 요소를 JNI 배열에 복사하는 역할을 합니다.

JNI는 Get/Release<Type>ArrayElements 함수들도 제공을 합니다. 여기서 <Type> 자리에 기본형 이름이 들어갑니다. 이 함수는 기본형 배열에 접근할 수 있는 포인터를 리턴합니다. 위의 예를 GetIntArrayElement를 사용해서 구현하면 아래와 같습니다.

JNIEXPORT jint JNICALL Java_ndk_intarray_IntArrayActivity_sumArray
(JNIEnv *env, jobject obj, jintArray arr)
{
jint *carr;
jint i, sum=0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0;
}
for (i = 0; i < 10; i++) {
sum += carr[i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum;

}

2. 오브젝트 배열

JNI는 오브젝트 배열에 접근하는 함수를 기본형 배열에 접근하는 함수와 별도로 제공을 합니다. GetObjectArrayElement는 지정한 인덱스의 오브젝트를 리턴합니다. SetObjectElement는 지정한 인덱스의 오브젝트 내용을 변경합니다. 기본형 배열을 다루는 JNI 함수와는 달리 전체 배열을 한번에 얻어오는 함수는 제공되지 않습니다.

아래의 예를 보도록 하겠습니다.

public class ObjectArrayTestActivity extends Activity {
private static native int[][] initInt2DArray(int size);

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

String str = new String("");

int[][] i2arr = initInt2DArray(3);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
str = str + " " + i2arr[i][j];
}
str = str + "\n";
}

TextView tv = new TextView(this);
tv.setText(str);
setContentView(tv);
}

static {
System.loadLibrary("ObjectArrayTest");
}
}

위 예에서 네이티브 메써드인 initInt2DArray는 int 형의 2차원 배열을 생성합니다. Java 코드에서는 이 생성된 배열을 받아와서 디스플레이를 해 줍니다.

initInt2DArray 의 네이티브 구현은 아래와 같습니다.

JNIEXPORT jobjectArray JNICALL Java_ndk_objectarraytest_ObjectArrayTestActivity_initInt2DArray
(JNIEnv *env , jclass cls, jint size)
{
jobjectArray result;
int i;
jclass intArrCls = (*env)->FindClass(env, "[I");
if (intArrCls == NULL) {
return NULL;
}
result = (*env)->NewObjectArray(env, size, intArrCls, NULL);
if ( result == NULL) {
return NULL;
}
for (i = 0; i < size ; i++) {
jint tmp[256];
int j;
jintArray iarr = (*env)->NewIntArray(env, size);
if (iarr == NULL) {
return NULL;
}
for (j=0; j < size; j++) {
tmp[j] = i+j;
}
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
(*env)->SetObjectArrayElement(env, result, i, iarr);
(*env)->DeleteLocalRef(env, iarr);
}
return result;
}

(*env)->FindClass(env, "[I");

FindClass JNI 함수는 int의 이차원 배열의 원소의 클래스를 찾습니다. 인자로 사용된 "[I"는 Java에서 ini[]와 같습니다. 이를 JNI 클래스 디스크립터라고 합니다.

(*env)->NewObjectArray(env, size, intArrCls, NULL);

NewObjectArray는 intArrCls형의 오브젝트를 배열의 요소로 갖는 배열을 생성합니다. 우리의 예에서는 FindClass로 얻어온 int[] 형의 데이터를 배열의 요소로 갖게 됩니다. int형의 배열은 NewIntArray를 사용해서 만들어야합니다.

(*env)->DeleteLocalRef(env, iarr);

DeleteLocalRef는 가상 머신이 메모리를 모두 소진해 버리는 일이 발생하지 않도록 하기 위해서 실행합니다. NewIntArray의 리턴으로 전달된 iarr은 더이상 필요가 없으므로 이를 가상 머신 상에서도 제거하여 메모리 낭비는 줄입니다.

'C++' 카테고리의 다른 글

문자열 비교는 == 비교연산자 와 strcmp 어떤걸 써야 하는가..  (0) 2013.10.10
자바 객체의 사용 - NDK  (0) 2012.12.27
JNI Basic for NDK -4  (0) 2012.12.27
JNI Basic for NDK -1  (0) 2012.12.27
NDK C++ 자료  (0) 2012.12.27
Posted by maysent
: