'jni'에 해당되는 글 5건

  1. 2011.12.02 jni vs jna (4)
  2. 2011.11.23 안드로이드 JNI 개발 준비 #2 (2)
  3. 2011.11.23 android ndk r7 버그
  4. 2011.11.23 안드로이드 JNI 개발 준비 #2
  5. 2011.11.22 안드로이드 JNI 개발 준비 #1 (1)

jni vs jna

java core 2011.12.02 15:06

 

 

Cassandra는 최근에 JNI(Java Native Interface) 대신 JNA (Java Native Access) 방식을 이용해서 native 영역에 메모리를 copy 하는 작업이 많은 경우에 사용되어 플랫폼의 속도를 향상시키는 방법을 쓰고 있습니다. 또한 일부 임베디드 프로젝트나 서버프로젝트에서 JNA 기법을 사용하는 예들이 많아져 오픈소스를 활용하는 저희에게 좋은 정보이면서 점차적으로 알아야 할 정보가 될 것 같아서 관련된 내용을 공유하고자 합니다.  (참고로 SWIG 이라는 라이브러리도 있는데, 제가 아직 몰라서 살펴보고 공유하겠습니다.)

 

1.     JNI (Java Native Interface)

JNIJava 에서 native 영역(c, c++)으로 들어가 호출 또는 native (c, c++)에서 java로 호출하는 interface입니다. c, c++ 언어로 만든 라이브러리, 솔루션을 바로 java에서 사용할 수 있습니다. 웹 서비스의 경우라면 c, c++ 언어로 만든 이미지 합성 또는 동영상 라이브러리를 사용하여 서비스하는 경우라 할 수 있습니다. 안드로이드 프레임워크의 경우에는 open gl과 같은 영역에서 연동할 수 있도록 되어 있습니다.

 

리눅스에서 JNI를 간단히 테스트하는 코드를 보겠습니다.

자바 클래스는 간단히 리눅스 shared object (so) 을 읽어 native 지시자로 구현된 method를 호출합니다 .

HelloJNI.java

class HelloJNI {
 native void printHello();
 native void printString(String str);
 
 static {
  System.load("/work/
JniTest/hellojni
.so");
 }
 public static void main(String[] args) {
  HelloJNI myJNI = new HelloJNI();
  myJNI.printHello();
  myJNI.printString("Hello in C");
 }
}


 

먼저 컴파일을 하고, native 구현해야 할 함수를 정의하는 header 파일을 생성합니다.

# javac HelloJNI.java

# javah HelloJNI

  

 

 

 

  

HelloJNI.h 파일에는 구현해야 할 “Java_HelloJNI_printHello”, “Java_HelloJNI_printString” 함수를 정의하고 있습니다.

 

<HelloJNI.h>

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class HelloJNI */

#ifndef _Included_HelloJNI

#define _Included_HelloJNI

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     HelloJNI

 * Method:    printHello

 * Signature: ()V

 */

JNIEXPORT void JNICALL Java_HelloJNI_printHello

  (JNIEnv *, jobject);

/*

 * Class:     HelloJNI

 * Method:    printString

 * Signature: (Ljava/lang/String;)V

 */

JNIEXPORT void JNICALL Java_HelloJNI_printString

  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus

}

#endif

#endif


 

그 다음은 HelloJNI.h 의 함수를 구현한 HelloJNI.c 파일을 간단히 작성합니다. printHello 함수는 단순히 문자열을 출력하고, printString 함수는 자바로부터 받은 문자열을 그대로 출력합니다.

 

<HelloJNI.c>

#include "HelloJNI.h"

 

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj) {

 printf("Hello World !!! jni\n");

 }

 

JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string) {

  const char *str = (*env)->GetStringUTFChars(env,string,0);

  printf("%s\n", str);

  return;

}


 

c코드를 컴파일하고 나서 shared object(so)로 만듭니다.

# gcc -c -I$JAVA_HOME/include -I$JAVA_HOME/include/linux HelloJNI.c

# gcc -shared -o HelloJNI.so HelloJNI.o

# ls -al HelloJNI.so

HelloJNI.so

 

이제는 마지막으로 자바를 실행합니다.

 

# java HelloJNI

Hello World !!!

Hello in C!!!

 

 

 

JNI 를 사용하기 위해서는 다음과 같이 6단계의 개발 단계를 거치게 됩니다. 번거롭지만 다들 JNI를 구현할 때는 이렇게 하고 있습니다.

  

 

JNI를 쓰는 이유는 java 영역에서 할 수 있는 부분을 Native 영역으로 이동하여 연동할 수 있고, 속도가 늦은 부분은 속도를 높일 수 있습니다. 안드로이드 플랫폼의 내부구조는 껍데기는 java이고, 내부는 JNI의 코드로 내부 모듈로 이루어져 있다고 할 수 있습니다.

JNI의 가장 큰 문제는 메모리 부분입니다. Native 로 구현한 공간에는 Auto GC를 하지 못하고 일일이 메모리 관리를 해야 하고, 잘못하면 메모리릭을 일어나게 할 수 있습니다. 또한 JVM 메모리를 침범하여 crash 가 되기도 합니다. 그래서 native 사용할 때는 항상 잘 사용해야 합니다.

 

2.     JNA (Java Native Access)

JNI 개발 측면에서는 번거로운 부분이 많은데, 이런 부분을 쉽게 해주는 API가 있는데, 바로 JNA 입니다. JNA libffi (Foreign function interface library)라 불리는 native library를 사용하여 dynamic하게 쓸 수 있게 합니다. Native 언어로 만들어진 함수를 사용하기 위해서  Header 파일 생성, Header 파일을 구현한 C소스, compile 과정이 없습니다. 번거로운 과정이 많이 생략 가능합니다.

어떻게 사용되고 동작되는지 살펴보겠습니다.

JNA 라이브러리는 원래 java.net(http://java.net/projects/jna/)에 있었는데, 지금은 github(https://github.com/twall/jna)로 옮겨진 상태입니다.

https://github.com/twall/jna/downloads 에 접근해서 jna.jar(https://github.com/downloads/twall/jna/jna.jar)를 다운받습니다.

 

위에서 언급했던 JNI 예제의 Native 코드를 생성합니다.

<myLib.h>

void printHello();

void printString(char* str);

 

<myLib.c>

#include <stdio.h>

void printHello() {

           printf("Hello World !!! jna\n");

}

 

void printString(char* str) {

           printf("%s\n", str);

}


 

이 파일들을 컴파일하여 shared object 파일로 만듭니다.

# gcc -c myLib.c

# gcc -shared -o myLib.so myLib.o

# ls myLib.so

 

이제는 myLib.so 파일을 정적으로 읽는 java 클래스를 하나 생성합니다. com.sun.jan.Library를 상속하는 interface 를 하나 만들고, shared object 를 읽어 자기 자신의 Instance 를 생성하여 header 파일을 생성하지 않고 바로 native 코드의 함수를 호출할 수 있도록 합니다.

 

<HelloWorld.java>

import com.sun.jna.Library;

import com.sun.jna.Native;

import com.sun.jna.Platform;

 

public class HelloWorld {

    public static void main(String[] args) {

        CLibrary.INSTANCE.printHello();

        CLibrary.INSTANCE.printString("Hi\n");

     }

}

 

interface CLibrary extends Library {

        CLibrary INSTANCE = (CLibrary) Native.loadLibrary(

            ("/home/kimyonghwan/myLib.so"), CLibrary.class);

           void printHello();

           void printString(String str);

}


 

그리고, 바로 classpath jna.jar와 현재 디렉토리를 추가하여 컴파일과 실행을 하면 예측한 대로 결과가 나옵니다.

 

# javac -classpath jna.jar HelloWorld.java

# java -classpath jna.jar:. HelloWorld

Hello World !!! jna

Hi

 

JNA 방식으로 코딩을 하니 훨씬 이해가 쉽습니다. 기존에 이미 만들어진 shared object를 바로 클래스만 작성해서 호출하는 방식이기 때문에 복잡한 interface가 필요가 없습니다. 또한 JAVA SRC 디렉토리에는 지저분한 C header 파일과 C 소스 파일은 더 이상 필요 없을 것입니다 .

 

 

그러나 jna의 한계가 있습니다. C++ 코드를 사용할 수 없습니다. (jnaerator라는 오픈소스가 C++로 변환하기는 합니다만, thirty party라 제외합니다.) 또한, api 특성상 JNI의 성경을 다 포함하지 못합니다. 예를 들어 native에서 jvm start하는 것은 JNA가 지원하지 않습니다.

단순히 Java단에서 C 코드로 일을 시킬 때 아주 편리합니다. 권한 만 있다면 리눅스 또는 윈도우 커널 라이브러리에 접근해서 interface에 바인딩하며 명령어를 실행할 수 있습니다. 또한 JNI에서 한 것처럼 C언어에서 함수에 Pointer를 연결하여 Java에서 쓸 수 있도록 있으며, Callback 이 일어나면 Java로 올려 처리도 가능합니다.

 

이해를 돕기 위해서 재미있는 예제를 하나 소개하겠습니다. 윈도우에서 제공하는 core library 중의 하나인 user32.dll 라이브러리를 읽어서 윈도우 OS의 현재 실행 중인 window 객체의 text를 얻어오는 예제입니다.

 

<User32Test.java>

import com.sun.jna.Native;

import com.sun.jna.Pointer;

import com.sun.jna.win32.StdCallLibrary;

 

public class User32Test {

           public interface User32 extends StdCallLibrary {

                     User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);

 

                     interface WNDENUMPROC extends StdCallCallback {

                                boolean callback(Pointer hWnd, Pointer arg);

                     }

 

                     boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer arg);

 

                     int GetWindowTextA(Pointer hWnd, byte[] lpString, int nMaxCount);

           }

 

           public static void main(String[] args) {

                     final User32 user32 = User32.INSTANCE;

                     user32.EnumWindows(new User32.WNDENUMPROC() {

                                int count;

 

                                public boolean callback(Pointer hWnd, Pointer userData) {

                                          byte[] windowText = new byte[512];

                                          user32.GetWindowTextA(hWnd, windowText, 512);

                                          String wText = Native.toString(windowText);

                                          wText = (wText.isEmpty()) ? "" : "; text: " + wText;

                                           System.out.println("Found window " + hWnd + ", total "

                                                                + ++count + wText);

                                          return true;

                                }

                     }, null);

           }

}


 

Eclipse에서 소스를 복사하고, build path jna.jar를 넣어주고 Run 을 실행하면 재미있는 결과가 나옵니다.

<결과>

..

Found window native@0x1074c, total 58; text: 네이버 백신

Found window native@0x10580, total 59; text: Network Flyout

Found window native@0x103fe, total 61; text: N드라이브 탐색기

..


 

샘플 코드의 예제를 도식화하면 JavaDll proxydll 간의 관계로 풀어낼 수 있습니다.

 

 

만약 User32window lock api를 사용하면 java에서도 쉽게 window locking이 되게 할 수 있습니다. Kernel32도 사용 가능합니다.

 

3.     결론

JNI 의 불편함대신 간편하게 사용될 수 있는 JNA는 점차 보편화 되고 있어서, 오픈소스 코드를 분석하거나 또는 Native 모듈을 개발하는데 큰 도움이 될 것입니다.

 

 

Posted by 김용환 '김용환'
TAG Java, JNA, jni


1. 안드로이드 JNI 로그 남기기

이클립스의 LogCat 에서 jni가 출력되는 것을 보고 싶을 때 유용한다.
jni의 native단에서 printf로 출력되는 것은 adb shell로만 볼 수 있다.


Android.mk에 다음을 추가한다.
LOCAL_LDLIBS := -llog

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := test
LOCAL_SRC_FILES := test.c
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)






C 코드에 다음을 추가한다.

#include <android/log.h>

__android_log_print(ANDROID_LOG_DEBUG, "Tag_Name", "Message...");

레벨은 많다.
ANDROID_LOG_UNKNOWN
ANDROID_LOG_DEFAULT
ANDROID_LOG_VERBOSE
ANDROID_LOG_DEBUG
ANDROID_LOG_INFO
ANDROID_LOG_WARN
ANDROID_LOG_ERROR
ANDROID_LOG_FATAL
ANDROID_LOG_SILENT




#include <jni.h>
#include <android/log.h>
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL Java_com_google_AddJniActivity_addJNI
  (JNIEnv *env, jobject thiz, jint num1, jint num2)
{
        __android_log_print(ANDROID_LOG_ERROR, "GOOGLE", "******Message...");
         return num1+num2;
}

#ifdef __cplusplus
}
#endif




logcat에 에러가 남는다.




2. 두 개의 모듈을 컴파일 해보기


java  ==  jni-c  이런 상황에서 jni-c 파일이 사용하는 라이브러리가 따로 있는 경우를 의미한다.
즉 jni에서 명시적으로 가지고 쓰는 경우를 의미한다.


first.h

#ifndef FIRST_H
#define FIRST_H

extern int first(int  x, int  y);

#endif /* FIRST_H */




first.c

#include <android/log.h>

int  first(int  x, int  y)
{
 __android_log_print(ANDROID_LOG_DEBUG, "google", "** first.c, first function ");
     return x + y;
}






jni 코드 : test.c

#include <jni.h>
#include <android/log.h>
#include "first.h"

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_google_TwoLibs
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_google_TwoLibs_add
  (JNIEnv *env, jobject thiz, jint x, jint y) {
 __android_log_print(ANDROID_LOG_DEBUG, "google", "*******Message...");
   return first(x, y);
}

#ifdef __cplusplus
}
#endif





이런 경우의 android make 파일은 다음과 같이 지정한다.

Android.mk

LOCAL_PATH := $(call my-dir)

## 1st 다음은 libwolib-first 모듈를 만들자
include $(CLEAR_VARS)

LOCAL_MODULE    := libtwolib-first
LOCAL_SRC_FILES := first.c
LOCAL_LDLIBS := -llog

include $(BUILD_STATIC_LIBRARY)

## 2nd 다음은 libwolib-second 모듈을 만들자

include $(CLEAR_VARS)

LOCAL_MODULE    := libtwolib-second
LOCAL_SRC_FILES := second.c
LOCAL_LDLIBS := -llog

LOCAL_STATIC_LIBRARIES := libtwolib-first

include $(BUILD_SHARED_LIBRARY)



obj/local/armeabi 디렉토리 밑에 보면, first.a 파일과 secod.so 파일이 있는지 확인가능하다.
static lib는 linux archive를 의미한다.

$  ls -al
libtwolib-first.a
libtwolib-second.so
objs

libs/eabi 디렉토리는 static library를 포함하는 so 파일들이 만들어져 있다.
libs/eabi/libtwolib-second.so 파일을 이클립스 프로젝트에서 복사해서 사용한다.


3. module을 export 하기

여러 모듈(c, header) 파일이 있을 때, 여러개의 so파일과 함께 jni가 make 파일을 이용하여 프로그래밍을 할 수 있다.


foo.h

#ifndef FOO_H
#define FOO_H

extern int  foo(int x);

#endif /* FOO_H */




foo.c


#include "foo.h"
#include <android/log.h>

/* FOO should be defined to '2' when building foo.c */
#ifndef FOO
#error FOO is not defined here !
#endif

# android.mk 파일에서 설정값에 parameter값을 넣어서 동작되는 지를 확인하는 코드

#if FOO != 2
#error FOO is incorrectly defined here !
#endif

#define  LOG_TAG    "libfoo"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

int  foo(int  x)
{
    LOGI("foo(%d) called !", x);
    return x+1;
}





bar.h


#ifndef BAR_H
#define BAR_H

/* FOO should be defined to '1' here with the magic of LOCAL_EXPORT_CFLAGS */
#ifndef FOO
#error FOO should be defined here !
#endif

#if FOO != 1
#error FOO is not correctly defined here !
#endif

extern int  bar(int  x);

#endif /* BAR_H */





bar.c

#include "bar.h"
#include <android/log.h>

#define LOG_TAG  "libbar"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

int bar(int x)
{
 LOGI("bar(%d) called!!", x);
 return foo(x)-1;
}




zoo.c


#include "bar.h"
#include <android/log.h>

int something(void)
{
 __android_log_print(ANDROID_LOG_INFO, "libzoo", "something() called!!");
 return bar(42);
}




test.c

#include <jni.h>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL Java_com_google_ModuleExports_foo
  (JNIEnv *env, jobject thiz, jint val)
{
 return foo(val);
}

JNIEXPORT jint JNICALL Java_com_google_ModuleExports_bar
  (JNIEnv *env, jobject thiz, jint val)
{
 return bar(val);
}

JNIEXPORT jint JNICALL Java_com_google_ModuleExports_zoo
  (JNIEnv *env, jobject thiz, jint val)
{
 return something();
}

#ifdef __cplusplus
}
#endif




java파일

class ActivityTest .... {

    public native int  foo(int val);
    public native int  bar(int val);
    public native int  zoo(int val);
   
    static {
        System.loadLibrary("bar");
        System.loadLibrary("zoo");
    }

}


Android.mk

LOCAL_PATH := $(call my-dir)

# FOO 값을 2로 만들어서  foo.c을 컴파일하고, FOO값을 1로 셋팅한후 archive 파일을 만든다.
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_CFLAGS := -DFOO=2
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/foo
LOCAL_EXPORT_CFLAGS := -DFOO=1
LOCAL_EXPORT_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)

# bar.c 를 컴파일하고 static 파일이었던 foo.a 를 모아 bar.so 파일로 만든다.
include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar/bar.c
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/bar
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)

# zoo 파일을 jni파일인 test.c 파일과 함께 컴파일하고, zoo.so 파일로 만든다.
include $(CLEAR_VARS)
LOCAL_MODULE := zoo
LOCAL_SRC_FILES := test.c zoo/zoo.c
LOCAL_SHARED_LIBRARIES := bar
include $(BUILD_SHARED_LIBRARY)



../../ndk-build 결과는 다음과 같은 so 파일이 생성된다.






 

Posted by 김용환 '김용환'

가장 따뜻한 11월 에 나온 ndk 7이다.

Linux 32/64-bit (x86) android-ndk-r7-linux-x86.tar.bz2


android nkd 7을 다운받아서 ndk 해보는데. 잘 안된다.  구글 검색해도 안나와서.. 좀 열받는다.

ndk 빌드하면 이렇게 아래로 나오는데..

/work/android-ndk-r7/samples/san-angeles# ../../ndk-build
/work/android-ndk-r7/prebuilt/linux-x86/bin/awk: 1: ELF : not found
/work/android-ndk-r7/prebuilt/linux-x86/bin/awk: 4: Syntax error: word unexpected (expecting ")")
Android NDK: Host 'awk' tool is outdated. Please define HOST_AWK to point to Gawk or Nawk !   
/work/android-ndk-r7/build/core/init.mk:258: *** Android NDK: Aborting.    .  Stop.



추적을 해보니. 어라. awk 문법에 맞는 것이 동작이 안된다.

/work/android-ndk-r7/samples/san-angeles# ../../build/awk/check-awk.awk
../../build/awk/check-awk.awk: line 22: BEGIN: command not found
../../build/awk/check-awk.awk: line 26: syntax error near unexpected token `s1,"world"'
../../build/awk/check-awk.awk: line 26: `    if (! match(s1,"world")) {'


awk도 최신 버전인데..

# apt-get install awk
Reading package lists... Done
Building dependency tree      
Reading state information... Done
Package awk is a virtual package provided by:
  original-awk 2010-05-23-1
  mawk 1.3.3-15ubuntu2
  gawk 1:3.1.7.dfsg-5

 




mk 파일보면서 android -ndk에 awk가 이상이 있나 확인해보았다.

32비트 x86 버전에서 prebuild/linux-x86/bin/awk 파일은 64 비트용이었다... 아하~

/work/android-ndk-r7/prebuilt/linux-x86/bin# file awk
awk: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped


awk 파일을 지우고 gawk를 링크를 걸어준다.

/work/android-ndk-r7/prebuilt/linux-x86/bin# mv awk awk.old
/work/android-ndk-r7/prebuilt/linux-x86/bin# ln -s /usr/bin/gawk awk

그리고, 샘플 디렉토리 가서 테스트해보니. 오예~ 빌드 완료.

/work/android-ndk-r7/samples/hello-jni# ../../ndk-build
Gdbserver      : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
Gdbsetup       : libs/armeabi/gdb.setup
Compile thumb  : hello-jni <= hello-jni.c
SharedLibrary  : libhello-jni.so
Install        : libhello-jni.so => libs/armeabi/libhello-jni.so
Posted by 김용환 '김용환'


http://developer.android.com/sdk/index.html 에 밑에 보면,
ndk 를 다운받을 수 있다.


mac, window(cygwin 이용), linux 버전있는데, linux로 설치해본다.


ndk7이 4.0을 포함한 최신 버전이지만, ubuntu10. 11에서 awk 관련해서 버그가 있다. 32비트에서만 이슈가 있음. 이 부분에 대한 해결방법은 여기에 있다.

나는 ndk 6b를 사용했지만, 위의 7도 동일하게 사용할 수 있다.


리눅스(ubuntu)에 android-ndk-r6b.tar.gz을 설치한다.
tar jxvf android-ndk-r6b.tar.gz

설치는 완료했다.

이제 코드쪽을 본다.
이클립스에서 클래스 하나를 생성하고, jni 함수를 하나를 만들고, 동적 library를 읽도록 한다.

package com.google;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloJni extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
  TextView tv = new TextView(this);
  tv.setText(stringFromJNI());
  setContentView(tv);
    }
    public native String stringFromJNI();
 static {
  System.loadLibrary("hello-jni");   
 }

}




이클립스 workspace의 프로젝트에서 컴파일된 디렉토리로 들어가서
class파일을 통해서 header 파일을 만든다.

프로젝트이름\bin> javah -classpath . com.google.JniHello

그러면, header 파일이 생성된 것을 확인할 수 있다.

> dir
com_google_JniHello.h

이 파일을 이용해서 c 소스를 만든다.

#include <string.h>
#include <jni.h>


jstring
Java_com_google_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}




이 파일을 linux에 설치한 ndk에 특정 위치에 둔다.

설치디렉토리/jni-test/knight/jni 폴더에 둔다.
그리고, make 파일인 Android.mk도 같이 집어 넣는다.

Android.mk 파일은 다음과 같다.

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)




 



설치디렉토리/jni-test/knight/ 디렉토리에서 빌드한다.

설치디렉토리/jni-test/knight # ../../ndk-build

컴파일이 잘되면 이렇게 so 파일이 나온다.
설치디렉토리/jni-test/knight/libs/armeabi/libhello-jni.so 파일이 생성된다.




so파일을 이클립스단으로 복사한다.
그 전에 먼저 jni 파일과 mk 파일을 복사한다.

eclipse 프로젝트의 jni 폴더를 생성하고, jni와 mk 파일을 복사한다.
그리고, so 파일을 libs에 복사한다.
디렉토리는 이렇게 나와야 한다.




이 파일에 대해서 에뮬에서 run하면 다음과 같은 결과가 나온다.




그리고, 보드에다가도 테스트해봤다.
ok 잘돈다.




소스는 아래와 같다.




ndk 를 보면, sample 디렉토리 밑에 많은 샘플(open gl 외 다양한 예제) 들이 있다.
참조해서 만들어보면 좋다.

san-angles 라는 샘플은 움직이는 예제를 보여준다. 터치해주면 잠깐 멈추고 그러니. 테스트하기에 재미있는 예제이다.



* 레퍼런스
 http://developer.android.com/sdk/ndk/index.html 

Posted by 김용환 '김용환'

혼자 잡담) java-jni-cpp 가지고 300만원짜리 알바까지 했는데..
왠걸 다시 하니 기억이 안난다. 오홋 새록새록해~ 그래도 어떻게 하나 열심히 공부해야지~^^




정리하는 내용
- 리눅스에서 java->c 호출
- 리눅스에서 c->java 호출


그래픽이나 c/c++로 만들어진 라이브러리들을 바로 사용할 수 있도록 하는 프레임웤이다.

HelloJNI 테스트 해본다.

class HelloJNI {
 native void printHello();
 native void printString(String str);
 
 static {
  System.load("/work/JniTest/hellojni.so");
 }
 public static void main(String[] args) {
  HelloJNI myJNI = new HelloJNI();
  myJNI.printHello();
  myJNI.printString("Hello in C");
 }
}



삼바를 이용해서 리눅스 /work/JniTest 디렉토리를 생성하고,  HelloJNI.java 복사한다.

컴파일하고, 그  class 파일을 이용해서 header 파일을 생성하도록 한다.
javac HelloJNI.java
javah HelloJNI


HelloJNI.h 파일이 생성

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif






HelloJNI.c 소스를 생성

#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj) {
 printf("Hello World !!! jni\n");
}


JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string) {
  const char *str = (*env)->GetStringUTFChars(env,string,0);
  printf("%s\n", str);
  return;
}





컴파일을 하여 so 파일이 만들어지도록 한다.

# gcc -c -I$JAVA_HOME/include -I$JAVA_HOME/include/linux HelloJNI.c
# gcc -shared -o HelloJNI.so HelloJNI.o
# ls -al HelloJNI.so
HelloJNI.so

 정상적으로 동작된다.

# java HelloJNI
Hello World !!!
Hello in C!!!

 


이런식으로 표현한 다양한 방식을 샘플로 적어본다.

java 코드

class JniFuncMain {
..
public static native JniTest createJniObject();
..
}

 

class JniTest
{
 private int intField;
 
 public JniTest(int num)
 {
  intField = num;
 }
 
 public int callByNative(int num)
 {
  return num;
 }
 
 public void callTest()
 {
  System.out.println("intField=" + intField);
 }
}



 cpp 코드


#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JniFuncMain
 * Method:    createJniObject
 * Signature: ()LJniTest;
 */
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
  (JNIEnv *env, jclass clazz)
{
 jclass targetClass;
 jmethodID mid;
 jobject newObject;
 jstring helloStr;
 jfieldID fid;
 jint staticIntField;
 jint result;
 
 // Get the Class staticIntField Value
 fid = env->GetStaticFieldID(clazz, "staticIntField", "I");
 staticIntField = env->GetStaticIntField(clazz, fid);
 printf("[CPP] Get JniFuncMain_Class_staticIntField!!\n");
 printf("[CPP] JniFuncMain_Class_staticIntField = %d\n", staticIntField);
 
 // Find the Class to create object
 targetClass = env->FindClass("JniTest");
 
 // Find the Constructor
 mid = env->GetMethodID(targetClass, "<init>", "(I)V");
 
 // Create a JniTest Object
 printf("[CPP] JniTest_Object Create!!\n");
 newObject = env->NewObject(targetClass, mid, 100);
 
 // Call the Method of Object
 mid = env->GetMethodID(targetClass, "callByNative", "(I)I");
 result = env->CallIntMethod(newObject, mid, 200);
 
 // Set the intField_field of JniObject
 fid = env->GetFieldID(targetClass, "intField", "I");
 env->SetIntField(newObject, fid, result);
 
 // return created Object
 return newObject;
}
#ifdef __cplusplus
}
#endif
 


# g++ -I/$JAVA_HOME/include -I/$JAVA_HOME/include/linux -c jnifunc.cpp
# g++ -shared -o jnifunc.so jnifunc.o
# java 메인클래스실행




이외에 c에서 java로도 호출이 가능하게 할 수 있다.


#include <jni.h>

int main()
{
 JNIEnv *env;
 JavaVM *vm;
 JavaVMInitArgs vm_args;
 JavaVMOption options[1];
 jint res;
 jclass cls;
 jmethodID mid;
 jstring jstr;
 jclass stringClass;
 jobjectArray args;
 
 // 1.JVM에 넘겨줄 아규먼트 셋팅 
 options[0].optionString = "-Djava.class.path=.";
 vm_args.version = 0x00010002;
 vm_args.options = options;
 vm_args.nOptions = 1;
 vm_args.ignoreUnrecognized = JNI_TRUE;
 
 //2.JVM 생성
 res = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);
 
 //3. 클래스 검색 과 로딩
 cls = (*env)->FindClass(env, "InvocationApiTest");
 
 //4. main 메소드 얻어오기 
 mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
 
 //5.메인메소드의 실제 파라미터값을 지정한다. 
 jstr = (*env)->NewStringUTF(env, "Hello Test from Native!!!");
 stringClass = (*env)->FindClass(env, "java/lang/String");
 args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
 
 //6.main 메소드를 호출한다.
 (*env)->CallStaticVoidMethod(env, cls, mid, args);
 
 //7.JVM를 내린다.
 (*vm)->DestroyJavaVM(vm);
}




클래스 파일

public class InvocationApiTest
{
 public static void main(String[] args)
 {
  System.out.println(args[0]);
 }
}


# gcc -I/$JAVA_HOME/include -I$JAVA_HOME/include/linux -c invocationApi.c

그다음은 jvm 관련 항목을 링크 옵션으로 줘야 jvm이 실행할 수 있다.
# gcc -L$JAVA_HOME/jre/lib/i386/client invocationApi.o -ljvm
(libjvm.so 라는 파일이 $JAVA_HOME/jre/lib/i386/client/libjvm.so 파일이 있어야 한다.)

dependent한 library 가 있는지를 확인한다.

# ldd a.out 

# export LD_LIBRRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/i386/client

# a.out
결과 출력


다음예는
jni onload 함수를 오버라이딩해서,
a라고 하는 함수를 b라는 함수로 호출되도록 바꿔치기 를 할 수 있는 예제이다.


// hellojnimap.cpp

#include <jni.h>
#include <stdio.h>

void printHelloNative(JNIEnv *env, jobject obj);
void printStringNative(JNIEnv *env, jobject obj, jstring string);

// JNI_ONload는 오버라이딩한 것이다.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
 JNIEnv *env = NULL;
 JNINativeMethod nm[2];
 jclass cls;
 jint result = -1;
 
 if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
  printf("Error");
  return JNI_ERR;
 }
 
 cls = env->FindClass("HelloJNI");
 
 nm[0].name = (char*)"printHello";
 nm[0].signature = (char*)"()V";
 nm[0].fnPtr = (void *)printHelloNative;
 
 nm[1].name = (char*)"printString";
 nm[1].signature = (char*)"(Ljava/lang/String;)V";
 nm[1].fnPtr = (void *)printStringNative;
 
 env->RegisterNatives(cls, nm, 2);
 
 return JNI_VERSION_1_4;
}

void printHelloNative(JNIEnv *env, jobject obj)
{
 printf("Hello World in C++!!\n");
 return;
}

void printStringNative(JNIEnv *env, jobject obj, jstring string)
{
 //const char *str = (*env)->GetStringUTFChars(env, string, 0);
 const char *str = env->GetStringUTFChars(string, 0);
 printf("%s\n", str);
 return;
}

#if 0
JNIEXPORT void JNICALL Java_HelloJNI_printHello
  (JNIEnv *env, jobject obj)
{
 printf("Hello World in C!!\n");
 return;
}

JNIEXPORT void JNICALL Java_HelloJNI_printString
  (JNIEnv *env, jobject obj, jstring string)
{
 const char *str = (*env)->GetStringUTFChars(env, string, 0);
 printf("%s\n", str);
 return;
}
#endif





//HelloJNI.java

class HelloJNI
{
 native void printHello();
 native void printString(String str);
 
 static {
  System.load("/work/JniTest/hellojnimap.so");
 }
 
 public static void main(String[] args) {
  HelloJNI myJNI = new HelloJNI();
  
  myJNI.printHello();
  myJNI.printString("Hello from c!!");
 }
 
}




# g++ -I/$JAVA_HOME/include -I$JAVA_HOME/include/linux -c hellojnimap.cpp
# g++ -shared -o hellojnimap.so hellojnimap.o
# java 메인클래스실행

Posted by 김용환 '김용환'