CLOVER🍀

That was when it all began.

倏䌑みの宿題は、JNI

特定の方向けの゚ントリです。経緯ずかは端折るので、読みにくい゚ントリかもしれたせんが、ご容赊を。

さお、ずある方から、JNIのサンプルコヌドを曞いお欲しいずいう䟝頌を受けたした。CC++には詳しい方なのですが、Javaはそうでもない様子。で、「Java-JNIでこういうむンタヌフェヌスで凊理をしたいんだけど、教えおくれない」のようなこずを聞かれたした。

垌望を聞いたJavaのnativeメ゜ッド宣蚀を、C蚀語的に衚蚘するず、こんな感じだず思いたす。

long int testfunc(int *num, char *s)

぀たり、longを返しお匕数は出力倉数ずしお曞き換えたい ず。

Javaで解釈するず、こうなりたすね。

int num = ...;
String s = ...;
long ret = testfunc(num, s);  // これでnumずsの倀が倉わる

Javaを知っおいれば分かるのですが、この仕様はJavaでは実珟䞍可胜です。なぜなら、Javaのプリミティブint, long, float, doubleなどなどおよびStringは、倉曎䞍可だからです。
※リフレクションを䜿甚しおどうのこうの、ずいうのはここでは眮いおおきたす
あず、プリミティブはポむンタ参照が扱えないので、そもそも参照枡し自䜓ができないですしね。

䟝頌をしおきた方は、詊行錯誀の結果 

int[] intArray = ....;
long ret = func1(intArray);  // ここで、intArrayの䞭身を倉曎
int intValue = intArray[0];
String stringValue = func2(intValue);

のように、nativeメ゜ッドを2぀に分割するこずで察凊したらしいですが、func1にintが枡せるずいいなヌず嘆いおおられたしたが、前述の通りintは倉曎䞍可なのでそれはできたせん。

ですので 倉曎可胜なクラスやパラメヌタ戻り倀にオブゞェクトを利甚しないずあたりスマヌトな解は出ないのですが、ちょっずだけサンプルを曞いおみたいず思いたす。

Eclipseは嫌いだったのず、すでに動く環境が手元にあるみたいなので端折りたす笑。

以埌、HelloJni.javaずいう名前のファむルで䜜業をしたす。最終的には、HelloJniクラス内にむンタヌクラスも定矩したす。
コンパむルコマンドは、以䞋の通りです。

$ javac HelloJni.java
$ javah HelloJni
$ gcc -fPIC -c HelloJni.cpp -I /usr/lib/jvm/java-6-sun/include/ -I /usr/lib/jvm/java-6-sun/include/linux/
$ gcc -shared HelloJni.o -o libHelloJni.so

JNIのヘッダファむルの堎所は、適宜読み替えおくださいね。

実行コマンドは、以䞋の通りです。

$java -Djava.library.path=[libHelloJni.soが眮いおあるディレクトリ] HelloJni 

なお、Javaファむル䞭に

System.loadLibrary("HelloJni");

ず蚘述するのをお忘れ無く。

圓方の実行環境は、以䞋の通りです。

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 11.04
Release:	11.04
Codename:	natty
$ uname -a
Linux ubuntu 2.6.38-10-generic #46-Ubuntu SMP Tue Jun 28 15:07:17 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux
$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)
$ gcc --version
gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

64bit Ubuntu Linuxです。

それでは、これからのサンプルではnativeメ゜ッドのむンタヌフェヌスは倉曎したすが、呌び出し回数は1回ですたせるようトラむしたす。

解法その1Objectの配列を䜿う

あたりキレむなやり方ではありたせん。Objectの配列をnativeメ゜ッドに枡すこずで、intずStringのパラメヌタを同時に枡し、か぀配列の芁玠が倉曎可胜なこずを利甚しお倀を曞き換える方法です。

Java偎はこちら。

    public native long writeObjectArray(Object[] objects);

        Object[] objects = {new Integer(5), "HelloWorld"};
        long ret = writeObjectArray(objects);
        System.out.println(String.format("num = %d, str = %s, ret = %d", objects[0], objects[1], ret));

C++偎はこちら。
※ヘッダファむルは端折りたす

JNIEXPORT jlong JNICALL Java_HelloJni_writeObjectArray
  (JNIEnv *env, jobject thisj, jobjectArray objects)
{
  jlong ret = 15;
  jclass integerClass = env->FindClass("java/lang/Integer");
  jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
  jmethodID intValueMethod = env->GetMethodID(integerClass, "intValue", "()I");
  
  jint intValue = env->CallIntMethod(env->GetObjectArrayElement(objects, 0), intValueMethod);
  jstring stringValue = (jstring)env->GetObjectArrayElement(objects, 1);
  const char *s = env->GetStringUTFChars(stringValue, NULL);

  printf("第1芁玠[%d], 第2芁玠[%s]\n", intValue, s);

  env->SetObjectArrayElement(objects, 0, env->NewObject(integerClass, integerConstructor, intValue * 2));
  env->SetObjectArrayElement(objects, 1, env->NewStringUTF("Hello JNI World"));

  env->ReleaseStringUTFChars(stringValue, s);

  return ret;
}

SetObjectArrayElementで、配列の芁玠を曞き換えおいるこずがポむントです。ホントはObject配列の䞭にintを詰めたかったのですが、そこからintを取埗するのが難しくお断念したした 。
実行するず、こうなりたす。

第1芁玠[5], 第2芁玠[HelloWorld]
num = 10, str = Hello JNI World, ret = 15
解法その2倉曎可胜なクラスを枡し、呌び出し先でその倀を倉曎する

settergetterを持ったクラスを定矩しお、それをメ゜ッドのパラメヌタずするこずでC++偎で倀を曞き換えたす。
FindClassはクラスを探すため、GetMethodIDはメ゜ッドを探すための関数です。

たず、HelloJniクラスのむンナヌクラスずしお、以䞋のようなクラスを定矩したす。
※別にむンナヌクラスである必芁はありたせん。別゜ヌスにするのが面倒だったので、むンナヌクラスを利甚しただけです

    public static class SendReceiveObject {
        private int intValue;
        private String stringValue;

        public void setIntValue(int intValue) {
            this.intValue = intValue;
        }

        public int getIntValue() {
            return intValue;
        }

        public void setStringValue(String stringValue) {
            this.stringValue = stringValue;
        }

        public String getStringValue() {
            return stringValue;
        }
    }

実際に利甚する時は、こんな感じです。

    public native long writeResult(SendReceiveObject sendReceive);

        SendReceiveObject sendReceive = new SendReceiveObject();
        sendReceive.setIntValue(5);
        sendReceive.setStringValue("Hello World");
        ret = writeResult(sendReceive);
        System.out.println(String.format("sendreceive.num = %d, sendreceive.str = %s, ret = %d",
                                         sendReceive.getIntValue(), sendReceive.getStringValue(), ret));

C++偎。

JNIEXPORT jlong JNICALL Java_HelloJni_writeResult
  (JNIEnv *env, jobject thisj, jobject sendReceive)
{
  jlong ret = 15;

  jclass sendReceiveClass = env->FindClass("HelloJni$SendReceiveObject");
  jmethodID getIntValueMethod, setIntValueMethod, getStringValueMethod, setStringValueMethod;

  getIntValueMethod = env->GetMethodID(sendReceiveClass, "getIntValue", "()I");
  setIntValueMethod = env->GetMethodID(sendReceiveClass, "setIntValue", "(I)V");
  getStringValueMethod = env->GetMethodID(sendReceiveClass, "getStringValue", "()Ljava/lang/String;");
  setStringValueMethod = env->GetMethodID(sendReceiveClass, "setStringValue", "(Ljava/lang/String;)V");

  jint intValue = env->CallIntMethod(sendReceive, getIntValueMethod);
  jstring stringValue = (jstring)env->CallObjectMethod(sendReceive, getStringValueMethod);
  const char *s = env->GetStringUTFChars(stringValue, NULL);

  printf("intValue[%d], stringValue[%s]\n", intValue, s);

  env->CallIntMethod(sendReceive, setIntValueMethod, intValue * 2);
  env->CallObjectMethod(sendReceive, setStringValueMethod, env->NewStringUTF("Hello JNI World"));

  env->ReleaseStringUTFChars(stringValue, s);

  return ret;
}

クラスやメ゜ッドを探さなくおはならないため、少々面倒ですが少なくずもJava偎はちゃんずintやStringの型で扱うこずができるようになりたす。

実行結果。

intValue[5], stringValue[Hello World]
sendreceive.num = 10, sendreceive.str = Hello JNI World, ret = 15

ただ、この方法の䞍満点は呌び出し偎で匕数の倀を勝手に倉曎するのは、最近のプログラミングスタむルずしおはどうなのかなぁず思いたす。

解法その3呌び出し先で新しいむンスタンスを生成しお返す

その2の発展圢です。匕数ずしおパラメヌタクラスを受け取り、その内容から新しいむンスタンスを生成しお返华したす。

ここでも、むンナヌクラスを利甚しおいたす。

    public static class ParamObject {
        private int intValue;
        private String stringValue;
        private long longValue;

        public ParamObject(int intValue, String stringValue) {
            this.intValue = intValue;
            this.stringValue = stringValue;
        }

        public ParamObject(int intValue, String stringValue, long longValue) {
            this(intValue, stringValue);
            this.longValue = longValue;
        }

        public int getIntValue() {
            return intValue;
        }

        public String getStringValue() {
            return stringValue;
        }

        public long getLongValue() {
            return longValue;
        }
    }

利甚偎はこちら。

    public native ParamObject createNewParam(ParamObject param);

        ParamObject param = new ParamObject(5, "HelloWorld");
        ParamObject result = createNewParam(param);
        System.out.println(String.format("result.num = %d, result.str = %s, result.ret = %d",
                                         result.getIntValue(), result.getStringValue(), result.getLongValue()));

C++偎はこちら。

JNIEXPORT jobject JNICALL Java_HelloJni_createNewParam
  (JNIEnv *env, jobject thisj, jobject param)
{
  jclass paramClass = env->FindClass("HelloJni$ParamObject");
  jmethodID getIntValueMethod, getStringValueMethod;
  jmethodID paramConstructor;

  getIntValueMethod = env->GetMethodID(paramClass, "getIntValue", "()I");
  getStringValueMethod = env->GetMethodID(paramClass, "getStringValue", "()Ljava/lang/String;");
  paramConstructor = env->GetMethodID(paramClass, "<init>", "(ILjava/lang/String;J)V");

  jint intValue = env->CallIntMethod(param, getIntValueMethod);
  jstring stringValue = (jstring)env->CallObjectMethod(param, getStringValueMethod);
  const char *s = env->GetStringUTFChars(stringValue, NULL);

  printf("intValue[%d], stringValue[%s]\n", intValue, s);

  jobject ret = env->NewObject(paramClass,
			       paramConstructor,
			       intValue * 2,
			       env->NewStringUTF("Hello JNI World"),
			       15);

  env->ReleaseStringUTFChars(stringValue, s);

  return ret;
}

匕数ParamObjectの内容を読み取り、その内容を元にStringは無芖しおいたすが 新しいParamObjectクラスのむンスタンスを生成しお返华したす。

実行結果はこちら。

intValue[5], stringValue[HelloWorld]
result.num = 10, result.str = Hello JNI World, result.ret = 15

パラメヌタのくせに戻り倀を持っおいるのが若干埮劙ですが、やっぱり新しいむンスタンスを生成しお返すスタむルの方がいいかなヌず思いたす。

完党な゜ヌスコヌド

䞀応、掲茉しおおきたす。
HelloJni.java

public class HelloJni {
    public static void main(String[] args) {
        new HelloJni().execute();   
    }

    public native long writeObjectArray(Object[] objects);
    public native long writeResult(SendReceiveObject sendReceive);
    public native ParamObject createNewParam(ParamObject param);

    public void execute() {
        System.loadLibrary("HelloJni");

        Object[] objects = {new Integer(5), "HelloWorld"};
        long ret = writeObjectArray(objects);
        System.out.println(String.format("num = %d, str = %s, ret = %d", objects[0], objects[1], ret));

        SendReceiveObject sendReceive = new SendReceiveObject();
        sendReceive.setIntValue(5);
        sendReceive.setStringValue("Hello World");
        ret = writeResult(sendReceive);
        System.out.println(String.format("sendreceive.num = %d, sendreceive.str = %s, ret = %d",
                                         sendReceive.getIntValue(), sendReceive.getStringValue(), ret));

        ParamObject param = new ParamObject(5, "HelloWorld");
        ParamObject result = createNewParam(param);
        System.out.println(String.format("result.num = %d, result.str = %s, result.ret = %d",
                                         result.getIntValue(), result.getStringValue(), result.getLongValue()));
    }

    public static class SendReceiveObject {
        private int intValue;
        private String stringValue;

        public void setIntValue(int intValue) {
            this.intValue = intValue;
        }

        public int getIntValue() {
            return intValue;
        }

        public void setStringValue(String stringValue) {
            this.stringValue = stringValue;
        }

        public String getStringValue() {
            return stringValue;
        }
    }

    public static class ParamObject {
        private int intValue;
        private String stringValue;
        private long longValue;

        public ParamObject(int intValue, String stringValue) {
            this.intValue = intValue;
            this.stringValue = stringValue;
        }

        public ParamObject(int intValue, String stringValue, long longValue) {
            this(intValue, stringValue);
            this.longValue = longValue;
        }

        public int getIntValue() {
            return intValue;
        }

        public String getStringValue() {
            return stringValue;
        }

        public long getLongValue() {
            return longValue;
        }
    }
}

HelloJni.cpp

#include "HelloJni.h"

JNIEXPORT jlong JNICALL Java_HelloJni_writeObjectArray
  (JNIEnv *env, jobject thisj, jobjectArray objects)
{
  jlong ret = 15;
  jclass integerClass = env->FindClass("java/lang/Integer");
  jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
  jmethodID intValueMethod = env->GetMethodID(integerClass, "intValue", "()I");
  
  jint intValue = env->CallIntMethod(env->GetObjectArrayElement(objects, 0), intValueMethod);
  jstring stringValue = (jstring)env->GetObjectArrayElement(objects, 1);
  const char *s = env->GetStringUTFChars(stringValue, NULL);

  printf("第1芁玠[%d], 第2芁玠[%s]\n", intValue, s);

  env->SetObjectArrayElement(objects, 0, env->NewObject(integerClass, integerConstructor, intValue * 2));
  env->SetObjectArrayElement(objects, 1, env->NewStringUTF("Hello JNI World"));

  env->ReleaseStringUTFChars(stringValue, s);

  return ret;
}

JNIEXPORT jlong JNICALL Java_HelloJni_writeResult
  (JNIEnv *env, jobject thisj, jobject sendReceive)
{
  jlong ret = 15;

  jclass sendReceiveClass = env->FindClass("HelloJni$SendReceiveObject");
  jmethodID getIntValueMethod, setIntValueMethod, getStringValueMethod, setStringValueMethod;

  getIntValueMethod = env->GetMethodID(sendReceiveClass, "getIntValue", "()I");
  setIntValueMethod = env->GetMethodID(sendReceiveClass, "setIntValue", "(I)V");
  getStringValueMethod = env->GetMethodID(sendReceiveClass, "getStringValue", "()Ljava/lang/String;");
  setStringValueMethod = env->GetMethodID(sendReceiveClass, "setStringValue", "(Ljava/lang/String;)V");

  jint intValue = env->CallIntMethod(sendReceive, getIntValueMethod);
  jstring stringValue = (jstring)env->CallObjectMethod(sendReceive, getStringValueMethod);
  const char *s = env->GetStringUTFChars(stringValue, NULL);

  printf("intValue[%d], stringValue[%s]\n", intValue, s);

  env->CallIntMethod(sendReceive, setIntValueMethod, intValue * 2);
  env->CallObjectMethod(sendReceive, setStringValueMethod, env->NewStringUTF("Hello JNI World"));

  env->ReleaseStringUTFChars(stringValue, s);

  return ret;
}

JNIEXPORT jobject JNICALL Java_HelloJni_createNewParam
  (JNIEnv *env, jobject thisj, jobject param)
{
  jclass paramClass = env->FindClass("HelloJni$ParamObject");
  jmethodID getIntValueMethod, getStringValueMethod;
  jmethodID paramConstructor;

  getIntValueMethod = env->GetMethodID(paramClass, "getIntValue", "()I");
  getStringValueMethod = env->GetMethodID(paramClass, "getStringValue", "()Ljava/lang/String;");
  paramConstructor = env->GetMethodID(paramClass, "<init>", "(ILjava/lang/String;J)V");

  jint intValue = env->CallIntMethod(param, getIntValueMethod);
  jstring stringValue = (jstring)env->CallObjectMethod(param, getStringValueMethod);
  const char *s = env->GetStringUTFChars(stringValue, NULL);

  printf("intValue[%d], stringValue[%s]\n", intValue, s);

  jobject ret = env->NewObject(paramClass,
			       paramConstructor,
			       intValue * 2,
			       env->NewStringUTF("Hello JNI World"),
			       15);

  env->ReleaseStringUTFChars(stringValue, s);

  return ret;
}

ちょっずでも参考になればよいのですが どうでしょ