Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Javaにおけるネイティブコード連携の各種手法の紹介

10,734 views

Published on

JJUG CCC 2015 Fallの「Javaにおけるネイティブコード連携の各種手法の紹介」での発表資料です。
#jjug_ccc #ccc_cd7

Published in: Technology
  • Be the first to comment

Javaにおけるネイティブコード連携の各種手法の紹介

  1. 1. Javaにおける ネイティブコード連携の 各種手法の紹介 NTTレゾナント・テクノロジー(株) 久納 孝治(ひさのう こうじ) hisano
  2. 2. 自己紹介  久納 孝治 (ひさのう こうじ)  NTTレゾナント・テクノロジーでRemote TestKitを作っています。 「今まで作ったもの」  世界初のケータイJava向けGameBoyエミュレータ (2002年のJavaOneで発表) http://www.itmedia.co.jp/mobile/0209/26/n_game.html  Skype社の公認のオフィシャルAPIに選定されたSkype4Java http://www.itmedia.co.jp/enterprise/articles/0710/05/news132.html  Pure JavaによるDalvik VMインタープリタ http://code.google.com/p/android-dalvik-vm-on-java/  MIDP→iアプリ変換ツール (Opera Miniのiアプリ版で採用) http://www.itmedia.co.jp/mobile/articles/1106/15/news106.html など
  3. 3. Pure JavaによるDalvik VMインタープリタ  AndroidのDalvik VMの仕様書から実装  Java ME上で動作するインタープリタ (知る限りではクリーンルーム実装では世界初)  サポートしている機能 Dalvik VMの全命令 Java ME CLDCのAPI群 wait・notify等を含むマルチスレッド関連(グリーンスレッドで実現)
  4. 4. MIDPアプリ→iアプリ変換ツール  Java MEのMIDPアプリのjarをiアプリのjarに静的変換するツール + MIDP APIのランタイム から構成  静的変換ツールはjavassistライブラリを用いて実装 ・javax.microeditionのクラス参照をtv.hisano.doja.runtime.midpパッケージに変換 ・System#getProperty呼び出しを独自のクラスメソッド呼び出しに変換 ・Class#getResourceAsStream呼び出しを独自のクラスメソッド呼び出しに変換 ・などなど  MIDP APIのランタイムをクリーンルーム実装 ・ピュアJavaのPNGデコーダを組み込み ・OpenGLを用いた高速な画面描画コンポーネントを組み込み  Opera社とOpera miniのiアプリ版としてリリース(現在は公開終了)
  5. 5. 本編
  6. 6. Javaでネイティブコードを使う意義  Javaで書かれていない豊富な各種ライブラリの利用 例: TensorFlow、Bonanza 弊社での利用例: コンピュータビジョンライブラリ(OpenCV)、iOS端末管理 (libimobiledevice)、ChromeのPDFエンジン(Pdfium)  OS特有の機能の利用 例: Windowsのジャンプ・リスト、OS Xの通知センター 弊社での利用例: プロセス情報取得(winp)、UNIXドメインソケット(junixsocket)  GPU・CPUをフルに使った高速化 例: DirectX、WebRTC 弊社での利用例: 音声・ビデオコーデック(OpenH264)  既存資産のポーティングの手間の軽減
  7. 7. ネイティブコードを組み込む方法 開発工数 品質 できること ① ラップした既存ライブラリを利用 ◎ ? △ ② Javaのみでネイティブ連携を行えるライブラリ を利用 ○ ○ ○ ③ JNIを利用 △ △ ◎
  8. 8. ラップした既存ライブラリを利用
  9. 9. GitHubで既存ライブラリを検索  アドバンスド検索 https://github.com/search/advanced  プレフィックスを用いた絞り込み https://help.github.com/categories/search/ 他の応用例: jnrとjnaどちらが使われているか? jna filename:pom.xml fork:false→3263件 jnr-ffi filename:pom.xml fork:false→47件
  10. 10. 利用例: JavaCV  JavaからOpenCVを使えるライブラリ  OpenCVにも本家Javaライブラリがあるものの、セットアップの容易さからJavaCV がオススメ  Apacheライセンス or クラスパス例外ありのGPLのデュアルライセンス 類似領域検索での利用例
  11. 11. 利用例: winp  JavaからWindowsのプロセス管理を行うためのライブラリ  Jenkinsを作られている川口さん作でMITライセンス  サポートしている機能  プロセス情報の取得  プロセスの強制終了  プロセスの優先順位変更  OSからのログオフ・リブート など プロセス一覧を表示するコード
  12. 12. Javaのみでネイティブ連携コードを 書けるライブラリを利用
  13. 13. この手法のメリット  JNIで連携コードを別途書く必要なし  OSごとのネイティブライブラリの準備が不要  Javaのデバッガを用いての開発が可能
  14. 14. com4j  COMコンポーネントをJavaから使えるようにするライブラリ  Jenkinsを作られている川口さん作  BSDライセンス ライブラリを生成 ライブラリを利用
  15. 15. SWTのinternal API  EclipseのGUIライブラリのSWTの下回り(org.eclipse.swt.internal)には大量のOS特 有コード  Windows: Win32/Win64  OS X: Cocoa Skype4Javaでの利用例
  16. 16. JNA  JavaインターフェイスでC関数を定義するとバインドしてくれるライブラリ  Apacheライセンス or LGPLのデュアルライセンス  似たライブラリとしてJNRあり ① JavaインターフェイスでC関数を定義 ② インターフェイス定義からプロキシを生成 ③ 実際の処理で利用
  17. 17. JNAのnativeメソッド定義による利用  nativeメソッドを定義したクラスをNative#registerメソッドに渡すだけで、JNIラ イブラリを用意したように呼び出すことが可能に pdfium4jでのnativeメソッド定義による利用例
  18. 18. JNAでのJavaリスナーの登録  Javaのリスナーを、Cの関数ポインタのようにコールバックとして登録可能
  19. 19. JNAでのAPI差分の吸収  JNAの各種処理をフックしてAPI差分を吸収することが可能 ・Javaのインスタンス→ネイティブオブジェクトの対応関係変更(TypeMapper) ・Javaのメソッド→C関数名の対応関係変更(FunctionMapper) ・C関数の呼び出し処理の前後に処理を追加(InvocationMapper) C関数名の変更を吸収する例
  20. 20. JNAerator  ヘッダーファイルからJNAを用いたコードを生成してくれるツール  JNAだけでなく、BridJ・Rococoa・node.js向けのコード出力も可能
  21. 21. JavaCPP  Javaクラス + アノテーションでC++クラスを定義すると、クラス定義を元にJNI コードを生成して使えるようにしてくれるライブラリ  JavaCVの下支えのライブラリ  Apacheライセンス or GPLのデュアルライセンス  javacpp-presetsリポジトリに豊富な定義が存在(CUDA、FFmpeg等)  TensorFlowの定義も追加! おそらく次のバージョンで楽に使えるように。 https://github.com/bytedeco/javacpp- presets/blob/master/tensorflow/src/main/java/org/bytedeco/javacpp/tensorflo w.java
  22. 22. 今まで紹介した手法の問題点  不正なポインタを利用するとプロセスごと落ちてしまう問題あり  各OS・CPUアーキテクチャごとにネイティブライブラリを用意する必要あり LLVMを調査している時にいい手法を思いついたので、 j2js2nc(Java to JavaScript to Native Code)ライブラリ を作ってみました!
  23. 23. LLVMの紹介  コンパイラ開発に必要な各種コンポーネントを提供するコンパイラ基盤 Clang: C/C++/Objective-Cから中間表現のIRを生成するコンポーネント LLVM Core: IRから各種CPU向けバイナリを生成するための各種コンポーネント LLDB: ClangやLLVM Coreを使って実装されたデバッガ  BSDライセンスに似た使いやすいライセンス  (ユーザ目線では新しいコンパイラと捉えるのがいいかも) Clang C/C++フロントエンド LLVM Optimizer x86バックエンド ARMバックエンド PowerPCバックエンド
  24. 24. Emscriptenの紹介  C/C++コードをJavaScriptにコンパイルするツール Unity等での応用例: https://github.com/kripken/emscripten/wiki/Porting- Examples-and-Demos Win 32 APIでの応用例: https://github.com/klutzy/win32.js MS-DOSゲーム: https://archive.org/details/softwarelibrary_msdos_games  Windows・OS X・Linux向けに提供  LLVMのバックエンドでCPU特有のバイナリではなく、JavaScriptコードを生成  JavaScriptサブセット仕様のasm.jsを用いて高速化 Clang C/C++フロントエンド LLVM Optimizer Emscriptenバックエンド(Fastcomp)
  25. 25. Emscriptenによるwin32.jsの実現方法 JavaScriptコード Emscriptenで変換 fake-mswin - JavaScriptライブラリ Win32エミュレーションライブラリ
  26. 26. j2js2ncライブラリの紹介  j2js2ncはEmscripten版JNA  Emscriptenを利用するメリット  ネイティブコードが、マシン語ではなくJavaScriptとなるため、OSやCPUに非依存  Nashorn上でマシン語相当が動作するため、不正なポインタによってプロセスが終了する ことがない  JNA 4.1の下回りを実装し直して実現しているため、JNAの豊富な高度な機能はそ のまま利用可能 Javaインスタンスの自動マッピング メソッド→関数のマッピングルール変更 など C/C++コード JavaScript Java Emscriptenで変換 生成されたJavaScriptコードはJava 8から搭載されたNashorn上で実行 Javaインターフェイス + j2js2ncでC関数を自動バインド
  27. 27. j2js2ncの具体的な利用の流れ #include <stdio.h> #include <stdlib.h> void hello(char *name) { printf("Hello, %s!n", name); } $ emcc hello.c -s EXPORTED_FUNCTIONS="['_hello']" -o js/hello.js package jp.hisano.sample; import jp.hisano.j2js2nc.Library; import jp.hisano.j2js2nc.Native; public class Main { public interface Hello extends Library { void hello(String name); } public static void main(String[] args) throws Exception { Hello library = (Hello) Native.loadLibrary("hello", Hello.class); library.hello("j2js2nc"); } } hello.dll等に相当するhello.jsを生成 Libraryインターフェイスを継承してメソッドを定義するだけ でC関数が利用可能
  28. 28. JNAとj2js2ncの内部処理の違い
  29. 29. JNAでの内部処理 #include <stdio.h> #include <stdlib.h> void hello(char *name) { printf("Hello, %s!n", name); } Hello library = (Hello) Native.loadLibrary("hello", Hello.class); library.hello("j2js2nc"); JNA処理 1. StringインスタンスをJNAオブジェクトに変換(NativeString インスタンスの生成) 2. JNAオブジェクトからネイティブオブジェクトに変換 (malloc関数でバイト配列を確保) 3. libffi経由でネイティブライブラリの関数を呼び出し GC実行時 1. finalizeメソッド実行時にfree関数が呼び出されて解放
  30. 30. j2js2ncでの内部処理 #include <stdio.h> #include <stdlib.h> void hello(char *name) { printf("Hello, %s!n", name); } Hello library = (Hello) Native.loadLibrary("hello", Hello.class); library.hello("j2js2nc"); J2js2ncの処理 1. Stringインスタンスをj2js2ncオブジェクトに変換 (NativeStringインスタンスの生成) 2. j2js2ncオブジェクトからネイティブオブジェクトに変換 (_malloc関数でバイト配列を確保) 3. javax.script経由でJavaScript関数を呼び出し GC実行時 1. finalizeメソッド実行時に_free関数が呼び出されて解放 JNA処理 1. StringインスタンスをJNAオブジェクトに変換(NativeString インスタンスの生成) 2. JNAオブジェクトからネイティブオブジェクトに変換 (malloc関数でバイト配列を確保) 3. libffi経由でネイティブライブラリの関数を呼び出し
  31. 31. いろいろな問題とその解決方法
  32. 32. ポインタのサポート ポインタは何バイト?  Emscriptenでは32-bitマシンとしてコンパイル  long型・wchar_t型・size_t型も、すべて32-bit 定義の初期化のメソッド
  33. 33. 64-bit整数型のサポート JavaScriptは64-bit浮動小数点型のみ、64-bit整数型の引数や戻り値は?  一つの64-bit整数引数を、二つの32-bitビット整数(実体は64-bit浮動小数点型)に分割  戻り値の64-bit整数の上位32-bitはtempRet0というグローバル変数に入れて返却 戻り値の処理 引数の処理
  34. 34. 処理速度が遅い 1バイトのメモリアクセスごとにJavaScriptのパース・実行処理が行われて低速 Nashornの内部APIを直接使う形にして高速化 (BufferArgumentsMarshalTestテストの実行時間が20秒から2.5秒に短縮!) 変更後変更前
  35. 35. 様々なネイティブ連携手法があるJavaは 最高です! ご清聴ありがとうございました。

×