2013年5月11日土曜日

JNI JavaからCppを実行する


JNI Javaからcppを呼び出してみる。

■まずはJavaでコード書きます
https://gist.github.com/masazdream/5560075

package jp.sp.jni;
public class HelloWorldJNI {
static {
System.loadLibrary("HelloWorldJNI");
}
public native String sayHelloWorld();
public static void main(String[] args){
HelloWorldJNI hello = new HelloWorldJNI();
System.out.println(hello.sayHelloWorld());
}
}

■対応するCppファイルを書きます
https://gist.github.com/masazdream/5560082


//============================================================================
// Name : HelloWorldJNI.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================
#include "jp_sp_jni_HelloWorldJNI.h"
JNIEXPORT jstring JNICALL
Java_jp_sp_jni_HelloWorldJNI_sayHelloWorld(JNIEnv *env, jobject obj){
return env->NewStringUTF("Hello World");
}
※Javaのコードとの名称対応は大切です。


■JavaとCppのコードから必要なヘッダファイルとSharedObjectを作成します
そのために今回はantを使用しますので、antからgccコンパイルする環境を作成します。

・ant-contribのバイナリをantのlibに配置
http://sourceforge.net/projects/ant-contrib/files/

・ant-xercesのバイナリを取得して、antoのlibに配置

http://xerces.apache.org/mirrors.cgi

・ant-cpptasksを作成する。
http://sourceforge.net/projects/ant-contrib/files/ant-contrib/cpptasks-1.0-beta5/


デフォルトでついてくるbuild.xmlに以下の対策を施して、実行するとjarが作成出来ます。
■対策
①javacタグにincludeAntRuntime="true"を設定します。
②ソースをコンパイルするJavaのバージョンに注意しないと警告が
うるさいので処理します。
A. java.sourceとjavac.targetには、コンパイルするJavaのバージョンを
設定しましょう。
B. jt.jarのパスを表すpathを作成して、javacのbootclasspathに指定します。
<path id="compile.boot.path">
  <fileset dir="${java.home}/lib">
    <include name="rt.jar" />
  </fileset>
</path>
<javac ・・・
  <bootclasspath refid="compile.boot.path" />
</javac>

■必要なパスを通す
③xercesのライブラリをクラスパスに追加する
<path id="compile.path">
  <fileset dir="[最初のステップでjarを置いたディレクトリ]">
    <include name="*.jar" />
  </fileset>
</path>
<javac ・・・
  <classpath refid="compile.path" />
</javac>

これでコンパイル出来ます。
target/lib/cpptasks.jarがあるので、ant本体のlibディレクトリに
コピーします。

> echo $ANT_HOME

でhomeを確認が出来たら、配下のlibにコピーします。

■antの記述を整えます。
javahでヘッダーファイル作成を記述します。
ccタスクでcppファイルをコンパイルします。

<taskdef resource="cpptasks.tasks">でccタスクを使用する準備が出来ますので、
以下のようにccタスクを記述します。

<cc link="shared" outfile="[soを出力するディレクトリ名]/[クラス名]" obj="[ヘッダーファイルを出力したディレクトリ]">
  <includepath location="${env.JAVA_HOME}/include}" />
  <includepath location="${env.JAVA_HOME}/include/linux" />
  <fileset dir="[ヘッダーファイルを出力したディレクトリ]" includes="HelloWorldJNI.cpp" />
  <libset libs="stdc++">  ←cppの場合は指定。CとCPPが混じっている場合には、cppファイルのみにantで実行出来ます。
</cc>

※outfileに指定したパスのファイル名をつかって、libhello.soなどの名前が決定されます。

最終的に以下の様なファイルになる。https://gist.github.com/masazdream/5560085

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- WARNING: Eclipse auto-generated file.
Any modifications will be overwritten.
To include a user specific buildfile here, simply create one in the same
directory with the processing instruction <?eclipse.ant.import?>
as the first entry and export the buildfile again. -->
<project basedir="." default="build" name="jni">
<property environment="env" />
<property name="ECLIPSE_HOME" value="../../eclipse" />
<property name="debuglevel" value="source,lines,vars" />
<property name="target" value="1.7" />
<property name="source" value="1.7" />
<path id="jni.classpath">
<pathelement location="bin" />
</path>
<target name="init">
<mkdir dir="bin" />
<copy includeemptydirs="false" todir="bin">
<fileset dir="src">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="bin" />
</target>
<target depends="clean" name="cleanall" />
<target depends="build-subprojects,build-project,makeh" name="build" />
<target name="build-subprojects" />
<target depends="init" name="build-project">
<echo message="${ant.project.name}: ${ant.file}" />
<javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
<src path="src" />
<classpath refid="jni.classpath" />
</javac>
</target>
<property name="h_dir" location="/root/workspace/jni/jnih" />
<target name="makeh">
<taskdef resource="cpptasks.tasks" />
<javah classpath="/root/workspace/jni/bin" class="jp.sp.jni.HelloWorldJNI" destdir="${h_dir}" />
<cc link="shared" outfile="/root/workspace/jni/dist/HelloWorldJNI" objdir="${h_dir}">
<includepath location="${env.JAVA_HOME}/include" />
<includepath location="${env.JAVA_HOME}/include/linux" />
<fileset dir="${h_dir}" includes="HelloWorldJNI.cpp" />
<libset libs="stdc++" />
</cc>
</target>
<target description="Build all projects which reference this project. Useful to propagate changes." name="build-refprojects" />
<target description="copy Eclipse compiler jars to ant lib directory" name="init-eclipse-compiler">
<copy todir="${ant.library.dir}">
<fileset dir="${ECLIPSE_HOME}/plugins" includes="org.eclipse.jdt.core_*.jar" />
</copy>
<unzip dest="${ant.library.dir}">
<patternset includes="jdtCompilerAdapter.jar" />
<fileset dir="${ECLIPSE_HOME}/plugins" includes="org.eclipse.jdt.core_*.jar" />
</unzip>
</target>
<target description="compile project with Eclipse compiler" name="build-eclipse-compiler">
<property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter" />
<antcall target="build" />
</target>
</project>
view raw build.xml hosted with ❤ by GitHub

■Javaの実行
soファイルの入ったディレクトリを環境変数「LD_LIBRARY_PATH」に指定してから実行します。

実行結果は、Hello World。
せっかくなのでcやcppで記述したファイルをドンドンJavaで実行していましょう。


0 件のコメント:

コメントを投稿