Comments
Description
Transcript
平成18年度卒業論文 3D ウィンドウマネージャの Java
平成18年度卒業論文 3D ウィンドウマネージャの Java 言語による 機能拡張手法 情報通信工学科 情報通信システム学講座 0020007 勝木貴志 指導教官 寺田実 助教授 提出日 2007年1月31日 概要 ● 研究目的 C言語のための API しか提供されていないプラグイン環境を Java で実装できるようにすることで、 プラグイン開発の難易度を下げる方法を提案し、それが実用的であるかどうかを評価する。 ● 方法 ネイティブな API を JNI によってラッピングし、Java から扱えるようにする。またプラグインの各種 呼出と Java クラスの間で橋渡しをする小さなプラグインをはさむことで、Java による実装を可能にし ている。 ● 結論 この方法での実現は可能であり、パフォーマンス的にも問題はない。だが、API のラッピングを手 動により行ったため API 全体をカバーするには到底至らず、ミドルウェアとして完成させるには、ラッ ピングを自動化する方法を導入する必要がある。 目次 1.序論.................................................................................................. ....................4 1.1.研究の背景.................................................................................. ....................4 1.2.現状のプラグイン環境の問題点............................................................................ 8 1.3.本研究の目的........................................................................................ ...........8 2.基礎技術.............................................................................................................. ..9 2.1.Java................................................................................ ................................9 2.2.JNI と Invocation API............................................................................... ..........12 2.3.Beryl Plugin......................................................................... ...........................13 3.関連研究.............................................................................................. ................16 3.1.Java Applet....................................................................................... ..............16 3.2.Java Plug-in Framework........................................................... .........................16 4.設計.................................................................................. ..................................17 4.1.実装方式の選択.............................................................................. ................17 4.2.実装概要........................................................................................................ 19 4.3.実装詳細........................................................................................................ 20 5.実装.................................................................................. ..................................24 5.1.JNI でのポインタの扱い................................................................ .....................24 5.2.具体例の実装........................................................................ .........................28 6.評価.................................................................................. ..................................33 6.1.コーディング................................................................................................. ...33 6.2.動作............................................................................................ ..................33 6.3.網羅性.................................................................................................. .........37 7.結論.................................................................................. ..................................38 7.1.考察............................................................................................ ..................38 7.2.今後の課題.................................................................. ..................................38 8.謝辞.................................................................................. ..................................39 9.参考文献.............................................................................................. ................40 10.付録........................................................................................................ ...........41 1. 序論 1.1. 研究の背景 1.1.1. 3D デスクトップ環境 近年、エンドユーザの計算機性能は目覚しいまでに発展し、特にエンタティメント(主にゲーム) に利用されるGPU(グラフィックスプロセッシングユニット)は以前のグラフィクスワークステーション を凌ぐほどである。 その一方で、3Dグラフィックスの利用方法として、デスクトップ環境が注目されている。(具体的 な実装例は後述。)まずデスクトップ環境とは、デスクトップマネージャとも呼ばれ、グラフィカルユー ザインターフェイスを提供するソフトウェアである。一般にアイコン、ウィンドウ、ツールバー、フォル ダ、壁紙などで構成され、マウスやキーボードによって操作される。従来の X Window System では ウィンドウマネージャがデスクトップ環境に近いが、現在ではウィンドウマネージャを含むGUI全体 をデスクトップ環境とすることが多い。3Dデスクトップ環境は、狭義にはデスクトップ環境を三次元 的なオブジェクトとして構成するものである。一方で広義には、GPUのハードウェアアクセラレーショ ンにより高速な描画を実現しているものである。 この3Dデスクトップ環境により、ウィンドウの透過や、奥行きのあるデスクトップなど各種3D効果 をデスクトップ環境に用いることができる。これらの視覚効果によりユーザビリティの向上が図れる だけでなく、デスクトップオペレーションに「楽しさ」や「面白さ」の要素を提供することができる。 その一方で、シンプルさの欠如、不必要な要求マシンスペックの増大などのデメリットが発生する 場合があることもまた事実であるが、それはユーザの選択によるものである。 1.1.2. 実装例 3 D デスクトップ環境には様々な実装が存在するが、以下に代表的な三つを挙げ、各実装の違 いと長所、短所をまとめる。 4 1.1.2.1. Beryl Beryl は Linux 環境向けの3Dデスクトップ環境である。 Beryl はもともと Novell 社によって発表された Compiz から派生した3Dデスクトップ環境であり、 Beryl Project というコミュニティベースのプロジェクトによって開発されている。 3Dデスクトップ環境全体は X サーバの OpenGL 拡張機能、OpenGL によって可能になった特 殊効果の処理を行うコンポジティングマネージャ、ウィンドウマネージャの三つからなり、Beryl はコ ンポジティングマネージャとウィンドウマネージャの機能をまとめたものである。OpenGL 拡張機能 には Xgl や AIGLX などを用いる方法や、nVidia 社製のドライバを用いる方法がある。 揺れるウィンドウ、回転するキューブ上の仮想デスクトップ(図1)、デスクトップに降り注ぐ雨滴な ど、各3 D 効果はそのひとつひとつがプラグインとして実装され、任意に追加することができる。 その長所は他の3Dデスクトップ環境と比較して、動作が軽いことである。また必要な効果のみを ユーザが選ぶことができるので、無駄がないともいえる。 またその短所は2007年1月現在まだ開発段階にあるソフトウェアであり、幾分不安定な面もある ことである。 本研究で取り上げるのが、この Beryl である。 図1 Beryl のプラグイン「Desktop Cube」 (出典:http://gentoo-wiki.com/HOWTO_XGL) 5 1.1.2.2. Looking Glass Looking Glass は Java による3Dデスクトップ環境の実装である。 Java3D をベースとしているため、OpenGL および DirectX をサポートする環境で動作する。Xサー バとして動作させることで、旧来のXクライアントアプリケーションを動作させることもできるが、 Looking Glass 専用アプリケーションは奥行きのある三次元のオブジェクトを表示させることができ る。この点が他の3Dデスクトップ環境大きな違いである。 その長所は Java で実装されているため実行環境の制限が少ないことである。Windows でも Linux でも動作させることができる。(ただし動作モードには制限がある。)また上記のように、 Looking Glass API を使って作られたアプリケーション(図2)は平面的なウィンドウの制限を受ける ことなく、立体的なオブジェクトを表示することができる。 またその短所は Java で実装されているためネイティブコードではなく、実行時に変換されるため 比較的動作が遅いことである。だが、バイトコード変換は起動後一度しか行われないので、常時起 動しているデスクトップ環境には影響は少ないと考えられる。 図2 Looking Glass API を使ったアプリケーション (出典:https://lg3d.dev.java.net/) 6 1.1.2.3. Windows Aero Windows Vista に搭載させている3Dデスクトップ環境である。 他の実装と比較して、提供されている機能が少なく動作が重いが Windows Vista に搭載されて いることで、認知度は高い。 その長所はこの三つの中では唯一の正規リリースされた製品であるので安定していることがあげ られる。 またその短所は上記のように、動作が重く、機能もありふれたものしかなく、ユーザによって新た に追加する方法も現状では提供されていない。 図3 Windows Aero (出典:http://www.microsoft.com/japan/windowsvista/experiences/aero.mspx) 7 1.2. 現状のプラグイン環境の問題点 本研究では上記の実装の中でも、機能追加が容易な Beryl に着目する。 上記にもあるように Beryl の各機能はそのひとつひとつがプラグインによって構成されている。だ が、Beryl のプラグイン開発は簡単ではない。その最大の理由はプラグインがC言語で記述される ことである。プラグインは beryl.h をインクルードすることで、Beryl が提供する機能や、OpenGL な どの各種 API(Application Program Interface)を使用できるが、当然それらもまたC言語のインター フェイスである。 C言語の特徴を Wikipedia から引用する。 C 言語は手続き型言語であり、コンパイラ言語として設計された。C 言語は、 自由度、実行速度、コンパイル速度などを追求したが、代わりにコンパイル後 のコードの安全性を犠牲にもしているので、コンピュータ寄り、あるいは玄人好 みとも言える言語仕様になっている。「扱いは難しく危険だが一旦使いこなせ ば非常に使い勝手が良くなる、よく切れる刃物のようなツール」という風に例え られることがある。 Wikipedia 「C 言語」 特に問題となるのが、ポインタの扱いとメモリ管理である。データを扱うためのポインタのみならず、 プラグインの製作には Beryl からプラグインの各関数へアクセスするため、関数ポインタを扱うこと が必要になる。同様にメモリ管理も重要である。一度起動したデスクトップ環境は長時間連続して 動作することが期待される。プラグインがメモリ管理に失敗し、必要のないメモリが確保されたまま になってしまうこと、大きなリソースの無駄遣いである。(これをメモリリークという。) 当然、これらはプラグイン製作者の技術レベルに依存する。C 言語による開発に卓越したプログ ラマであれば、これらの問題を考慮した上でコーディングを行えるだろう。しかし、これらは本来の プラグインのロジックには無関係である。C 言語による実装ではなく、ポインタがなく、メモリ管理が 自動化された言語であれば、プラグイン製作者はプラグインのロジックにのみ注力することができ る。 1.3. 本研究の目的 よって本研究では、Beryl プラグインの開発難易度を下げるため、Java によってプラグインを開発 することができるミドルウェアを提案し、その有効性を実証することを目的とする。 8 2. 基礎技術 2.1. Java 本研究がもたらす利点は、Java がもたらす利点そのものである。 Java とは、狭義ではプログラミング言語 Java であり、広義ではプログラミング言語 Java のプログラ ムの実行環境および開発環境である。 プログラミング言語としての Java は以下のような特徴を持つ。 ● オブジェクト指向プログラミング(OOP) ● 統一された標準クラスライブラリ ● シンプルな言語仕様 また実行環境としての Java は以下のような特徴を持つ。 ● バイトコードを実行する仮想マシン ● プラットフォーム非依存 ● ガーベジコレクションによるメモリ管理 ● 実行時のセキュリティ機能 2.1.1. OOP Java はオブジェクト指向プログラミング(OOP)言語である。 Wikipedia から「オブジェクト指向プログラミング」の概要を引用する。 オブジェクト指向プログラミング (object-oriented programming, OOP) とは相互にメッセー ジ (message) を送りあうオブジェクト (object) の集まりとしてプログラムを構成する技法であ る。この技法をサポートするプログラミング言語はオブジェクト指向言語 (object-oriented programming language, OOPL) と呼ばれる。オブジェクト指向プログラミングには必ずしも オブジェクト指向言語を用いる必要は無いが、オブジェクト指向言語の備えるクラスとその 継承などの仕組みを利用するほうが格段に開発効率は向上する。 Wikipedia 「オブジェクト指向プログラミング」 9 Java のOOPでは単一のクラス(Object クラス)を基底とした単一継承である。 2.1.2. JVM JVMとは Java 仮想マシン(Java Virtual Machine)であり、Java の実行環境である。Java の実行ファ イルであるクラスファイルを読み込み、バイトコードを CPU が実行できるネイティブコードへ変換す る役割を負っている。 初期のJVMはインタプリタであったため低速であったが、現在では JIT、HOTSPOT などのバイト コードコンパイラにより、実行時にネイティブコードに変換される。このため初めて実行するコード は、コンパイルに必要な時間がかかるため、若干時間が掛かってしまう。だが、同じコードを二回 目に実行する際には、前回のコンパイル結果を用いるため高速である。このバイトコードコンパイ ルがあるため、Java は連続実行に強く、散発的に起動・終了を繰り返す実行には弱いという特性 を持つ。 また、メモリ管理もJVMの仕事である。Java のメモリ管理は自動化ガーベジコレクションによって 自動的に行われる。これによりメモリリークは起こりにくくなるが、不必要にも係わらず参照が残った ままになっているオブジェクトが存在する場合には、ガーベジコレクションの対象とならずメモリリー クが発生することになる。またガーベジコレクションが実行されるタイミングは、JVMの実装依存で あり、一般に実行中の Java プログラムから知ることはできない。よって解放タイミングがシビアな資 源を管理するオブジェクトは、ガーベジコレクションに頼らず、明示的に解放タイミングを指定する 必要がある。(例えばストリームオブジェクトなど。) 10 2.1.3. 標準クラスライブラリ Java には統一されたクラスライブラリが標準で存在する。よって開発ツールとしての利便性が高く、 生産性が高い。 図4 Java プログラムに共通の環境を提供する Java VM (出典:http://www.atmarkit.co.jp/fjava/rensai3/javavm01/javavm01_1.html) 11 2.2. JNI と Invocation API 2.2.1. JNI JNI は Java からネイティブコードを実行するためのインターフェイスである。JNI を使わない場合、 Java のプログラムは標準クラスライブラリによって用意されている API しか使うことができない。標準 クラスライブラリが提供していない機能や、特定の環境に依存した機能を Java から利用するため には、JNI を使うことになる。 JNI にはふたつの使い方があり、ひとつは Java からネイティブコードを実行する方法、もうひとつ はネイティブコードから Java の機能を使う方法(後述の Invocation API)がある。本研究ではその 両方が必要となる。 またデメリットとしては、環境依存のコードを含んでしまうことにより可搬性が失われる。一般に JNI は呼出コストが高い。 2.2.2. Invocation API Invocation API はネイティブコード上で JVM を構築し、Java バイトコードを実行するための API である。この API を使うことで、Java で書かれた資源をネイティブコードから利用することが可能に なる。 12 2.3. Beryl Plugin 2.3.1. Beryl Plugin とは Beryl プラグインは、Beryl が提供する各機能を実現するコードである。機能ごとに別々のプラグ インとして実装されているので、例えば Desktop Cube のプラグイン、Water Effect のプラグインと いったように分かれている。 その実体はダイナミックリンクライブラリであり、必要時に Beryl がロードして実行する。 プラグインは関数の集まりとなっており、Beryl が発生したイベントごとに対応した関数を呼び出 すことで実行される。その呼出構造を以下にまとめる。 13 2.3.2. プラグインの呼出構造 Beryl から最初に呼び出される関数は「CompPluginVTable* getCompPluginInfo(void)」である。こ の関数は CompPluginVTable を返すが、これは Plugin を構成する関数や、各パラメータを定義し ている。 以下に CompPluginVTable の定義を引用する。 typedef struct _CompPluginVTable { char *name; char *shortDesc; char *longDesc; InitPluginProc init; FiniPluginProc fini; InitPluginForDisplayProc initDisplay; FiniPluginForDisplayProc finiDisplay; InitPluginForScreenProc initScreen; FiniPluginForScreenProc finiScreen; InitPluginForWindowProc initWindow; FiniPluginForWindowProc finiWindow; GetDisplayOptionsProc getDisplayOptions; SetDisplayOptionProc setDisplayOption; GetScreenOptionsProc getScreenOptions; SetScreenOptionProc setScreenOption; CompPluginDep *deps; int nDeps; CompPluginFeature *features; int nFeatures; unsigned int version; unsigned int struct_plugin_size; unsigned int struct_display_size; unsigned int struct_screen_size; unsigned int struct_window_size; unsigned int struct_texture_size; unsigned int struct_icon_size; char * gettext_domain; } CompPluginVTable; この中で「……Proc」とあるものが、プラグインで定義される関数ポインタの型である。この12個の 関数がそれぞれ別々のタイミングで Beryl から呼び出され、プラグインを構成する。 一般には、必要な関数はこれだけではない。プラグインによっては、マウスイベント、キーイベント などを必要とする。その場合には別に定義し、Beryl に与えてやる必要がある。 14 2.3.3. 実装 Beryl プラグインの実装は多種存在する。Berylをインストールする際に付属しているプラグイン がメジャーだが、それ以外にも Beryl Project 内部、外部の開発によるプラグインが存在する。 以下に具体的なプラグインの実装例を挙げる。 2.3.4. 実装例「Desktop Cube」 プラグインの実装例として、立方体の各面にバーチャルデスクトップが配置される Desktop Cube(図1)を取り上げる。 Desktop Cube では、図1のように立方体の側面に各バーチャルデスクトップが割り振られ、デス クトップを切替えると立方体が回転し、別の側面に切り替わる効果である。 各デスクトップ間をウィンドウが移動する際には、デスクトップの端にウィンドウを移動させると、立 方体が回転し次のデスクトップに移動することができる。 2.3.5. 実装例「Water Effect」 Water Effect はウィンドウを配置した際に、波紋状の効果がデスクトップ上に描かれる効果である。 図5 Water Effect が動作している様子 15 3. 関連研究 3.1. Java Applet 本研究の動作方法はWEBブラウザ上で動作する Java Applet に類似点を見出すことができる。 アプレット自体はブラウザから提供された資源を使い、Java の API を組み合わせてプラグラムを構 成する。その出力がブラウザにフィードバックされる点も似ているといえる。 本研究はブラウザが提供する API と Applet が用いる API を橋渡しする部分に相当する。 3.2. Java Plug-in Framework Java Plugin Framework(以下 JPF)は Eclipse のプラグイン機構をベースにしたフレームワークで あり、Java を用いたプラグイン機構を簡単に実現することができる。 以下に Java Plug-in Framework の概要を引用する。 Java Plug-in Framework (JPF) は動的に “plug-in” を発見し、ロードするラ ンタイムエンジンを提供する。plug-in は"約束"に従って JPF に自身の情報 を与える構造化されたコンポーネントである。JPF は利用可能な plug-in や それらの plug-in が与える関数のレジストリを保持する。 テクノロジニュース : Java Plugin Framework (JPF) 0.9 リリース – TECHSCORE http://www.techscore.com/forum/modules/wordpress1/index.php?p=64 だが、JPF は Java プログラムに対するプラグインなので本研究に組み込むためには、ネイティブ コードとの間で橋渡しが必要になる。 16 4. 設計 4.1. 実装方式の選択 本研究を実装するにあたり、次の二つの方法が考えられる。 4.1.1. 通信を用いた実装 Beryl とはまったく別のプロセスとして JVM を起動し、プラグインのコードをもった Java プログラム をロード後、通信路を開けるように待機する。Beryl 上では通信機能だけをもった小さなネイティブ コードのプラグインを実行する。Beryl がそのプラグインの各関数を実行すると、プラグインは外部 プロセスとして動作している Java プログラムに通信路を確立し、メッセージを交換することによって、 プラグインの機能を構成する。 Event Driven Java Program Result Small Plugin 通信路 JVM Beryl 図6 通信を用いた実装 この通信路にはネットワーク通信や、dbus などのメッセージ通信が利用できる。またこの様な実 装方法は RPC(Remote Procedure Call)などの実装例がある。 JVM を切り離すことにより、構造が単純になり、デバッグもしやすいと考えられる。 一方で、通信にかかるコストのため、動作が遅いことが予想される。 17 4.1.2. JNI を用いた実装 JNI を用いた方法とは、ネイティブなプラグイン上に Invocation API を用いて JVM を構築し、プラ グインの動作に必要なデータや関数を Java が扱えるようにラッピングして実装する方法である。 この方法ではプロセスは Beryl のプロセスのみであり、Beryl とそのプラグインはメモリを共有した 状態となる。 この方法の利点は、通信路を開く必要がないので前出の方法より比較的高速であることだが、そ の一方で Beryl のプロセスの一部になってしまうことで、デバッグしにくくくなってしまう欠点もある。 インタラクティブな3Dデスクトップのプラグインであることを鑑みると、通信路による遅延はユーザ の操作を阻害することが考えられる。よって、本研究ではこの方法を採用する 18 4.2. 実装概要 プラグインの概要を次の図7に示す。 Standard Plugin (Native) Java Plugin (Byte Code) Mismatch! Java Plugin (Byte Code) Wrapper Plugin (Native) Beryl Plugin Interface Beryl OpenGL Extension 図7 プラグインの概要 図7中の一番左は一般的な Beryl プラグインの例である。中央で Java によるプラグインを Beryl のプラグイン API 上に配置しようとしているが、バイトコードである Java のコードを直接使うことはで きない。よって一番右のように、ラッパープラグインを間に挟み、ネイティブコードと Java コードの仲 介役をさせる。 ここでプラグインの構成要素の名前を定義する。 名称 意味 ベースプラグイン JVM を起動しユーザプラグインを実行する小さな Beryl プラグイン。 図7中で「Wrapper Plugin」と記述しているところ。 ラッピング API Beryl プラグインが使うことのできる関数や構造体などを、Java 使えるよ うにラッピングしたもの。 ユーザプラグイン 本研究で作成するミドルウェアを使って動作する Java プログラム。 図7中で「Java Plugin」と記述しているところ。 またこのミドルウェア全体を「Jadeite」と呼称する。Beryl 周辺ソフトウェア名が宝石名を取ってい ることに由来する。 19 4.3. 実装詳細 実装の詳細を以下の図8に示す。 User Plugin (Java) Plugin Base Class (Java) JVM Base Plugin (Native) Beryl Plugin Interface Beryl OpenGL Extension 図8 実装詳細 基本的な構造に関しては前項の「実装概要」で述べた通りだが、図7中の Java Plugin の部分が JVM と Plugin Base Class と User Plugin で構成される。プラグインベースクラスはユーザプラグイン の基底クラスであり、共通機能の実装や、ユーザプラグインが最低限実装すべきメソッドを定義し ている。 20 4.3.1. 呼出構造 もともとの Beryl プラグインの呼出は以下の通りである。 呼出 Beryl API Plugin API 使用 図9.1 呼出構造1 そこで Jadeite を使った呼出構造は以下の様になる。 呼出 ベースプラグイン 呼出 User Plugin (Java) Beryl API API 使用 ラッピング API API 使用 図9.2 呼出構造2 まず Beryl からイベントが開始されベースプラグインの対応する関数が呼び出される。次にベー スプラグインの関数は、自身に対応するユーザプラグインのメソッドを JVM を使って呼び出す。ユー ザプラグインは呼び出されたイベントの必要なロジックを実行するため、各種 API を使う。API を実 行した影響は Beryl 側に及ぼされ、イベントロジックが反映される。 4.3.2. ベースプラグイン ベースプラグインの主な役割は次の四つである。 1. JVM の準備、起動 21 2. ユーザクラスのロード 3. イベント処理(ユーザクラスのメソッド呼出) 4. JVM の終了 また Beryl からの呼出をユーザプラグインのメソッドに対応させる際、Beryl から渡される各変数を ラッピング API(詳細は後述)を用いて、Java が扱える形に変換する必要がある。 ベースプラグインに必要な各機能はそれぞれ関数化し、jadeite.h に書くことにする。 4.3.3. プラグインベースクラス プラグインベースクラスはユーザプラグインの基底クラスとなるクラスである。以下にその定義を引 用する。 public abstract class Jadeite { public abstract boolean Init(); public abstract void Fini(); public abstract void InitDisplay(); public abstract void FiniDisplay(); public abstract void InitScreen(); public abstract void FiniScreen(); public abstract void InitWindow(); public abstract void FiniWindow(); public abstract void GetDisplayOptions(); public abstract void SetDisplayOption(); public abstract void GetScreenOptions(); public abstract void SetScreenOption(); public abstract void HandleMotionEvent(final CompScreen cs, final int xRoot, final int yRoot); } プラグインベースクラスはクラス名「Jadeite」という抽象クラスになっている。抽象クラスとは、それ 自身はインスタンス化できないクラスであり、abstract キーワードを用いて定義する。抽象クラスを 実装する子クラス(本研究の場合ではユーザプラグインクラス)は、必ず abstract キーワードが付い たメソッドをオーバーライドする必要がある。そのためベースプラグインがユーザプラグインをロード 22 した際に、実行できるメソッド名、及びその型が分かるのである。 この例では共通処理がないために全てのメソッドが abstract となっているので、抽象クラスではな く interface でも構わないが、共通処理も実装できるように抽象クラスとした。 4.3.4. ラッピング API 4.3.4.1. データのラップ ユーザプラグインからネイティブな構造体や、ポインタを扱うためには工夫が必要になる。詳細は 後述の「JNI でのポインタの扱い」を参照。 よってラッピング API では、取り扱う可能性のある全ての構造体やポインタを、その使いかたに応 じて個々にラッピングし、Java から使えるようにしなければならない。 4.3.4.2. 関数のラップ データのラッピングと同様に関数もラッピングする必要がある。ネイティブ関数を実行する際には、 ラッピングして渡させたデータを展開する必要がある。 23 5. 実装 5.1. JNI でのポインタの扱い まず実装するにあたって、Invocation API を用いた場合のポインタの扱いについて考える。当然 ながら Java のコードにはポインタが存在しない。だが図9を見ると Beryl から呼び出された際に与 えられた変数は、API がコールされる前に Java のコードの中で保持しなければならない。 そこで Java のコードの中でポインタを保持する方法を考える。ポインタとは、メモリアドレスを持つ 変数である。メモリアドレス自体はただの数値に過ぎない。よって Java のコードの中では、数値とし て処理することを考える。 ここで Java のプリミティブ型の内、int は 32bit、long は 64bit であるので、long を使うことにする。 Beryl からポインタを渡されたベースプラグインは、これを jni.h で定義される jlong(Java コード上 の long)にキャストし、以下の様なクラスのコンストラクタに渡す。 public class Pointer { private long pointer; public Pointer(final long pointer) { this.pointer = pointer; } public long getValue() { return pointer; } private Pointer() { } } これを各ポインターの基底クラスとする。実際に各ポインターをラッピングする時には、コンパイラ の型チェック機能を生かすため、この Pointer クラスを派生して個々のポインタクラスを作って用い る。 そして API でポインタを使う際には、Pointer クラスの派生クラスから long 値としてポインタを取り 出し、元のポインタの型にキャストしてから目的の関数に引き渡し、実行する。 24 5.1.1. REGION 構造体の例 ここで実際に使用した REGION 構造体を例に挙げる。 public class REGION extends Pointer{ static { System.loadLibrary("REGION"); } private static native long newInstance(); private static native long deleteInstance(final long pointer); private native void setRectsNative(final long pointer, final long rectsPointer); private native long getExtentsPointerNative(final long pointer); private native void setNumRectsNative(final long pointer, final int i); public REGION() { super(REGION.newInstance()); } public void finalize() { REGION.deleteInstance(super.getValue()); } public void setRects(final long pointer) { this.setRectsNative(super.getValue(), pointer); } public long getExtentsPointer() { return this.getExtentsPointerNative(super.getValue()); } public void setNumRects(final int i) { this.setNumRectsNative(super.getValue(), i); } } これが REGION 構造体を Java から扱うためのラッパークラスである。Pointer クラスを継承し必要 な Getter と Setter を持っている。実際に値を操作しているのは、native キーワードの付いたメソッ ドであり、これは JNI によって実装されたネイティブコードである。次にこのネイティブコードを示す。 25 #include "REGION.h" #include <beryl.h> JNIEXPORT jlong JNICALL Java_REGION_newInstance (JNIEnv *env , jclass cls) { return (jlong)malloc(sizeof(REGION)); } JNIEXPORT jlong JNICALL Java_REGION_deleteInstance (JNIEnv *env, jclass cls, jlong pointer) { REGION* p = (REGION*)pointer; free(p); } JNIEXPORT void JNICALL Java_REGION_setRectsNative (JNIEnv *env, jobject obj, jlong pointer, jlong rectsPointer) { REGION* p = (REGION*)pointer; p->rects = (BOX*)rectsPointer; } JNIEXPORT jlong JNICALL Java_REGION_getExtentsPointerNative (JNIEnv *env, jobject obj, jlong pointer) { REGION* p = (REGION*)pointer; return (jlong)(&(p->extents)); } JNIEXPORT void JNICALL Java_REGION_setNumRectsNative (JNIEnv *env, jobject obj, jlong pointer, jint i) { REGION* p = (REGION*)pointer; p->numRects = i; } 26 まず、beryl.h をインクルードしている。これによって Beryl の API を使うことができる。 次に Java_REGION_newInstance()に注目する。この関数では malloc()を使って、REGION 構造体 に必要なメモリを確保している。malloc()の返値は確保したメモリの番地であるので、これを jlong に キャストし返している。Java 側のコードを追うと、この関数が Java の REGION クラスのコンストラクタ から呼ばれていることが分かる。よって、Java のコードで REGION クラスをインスタンス化することで、 ネイティブコードでは REGION 構造体分のメモリが確保され、Java のコードには long 値として返っ てくることになる。 次に Java_REGION_deleteInstance()がある。これは Java のコードでは finalize()に対応する。 finalize()は JVM がガーベジコレクションを実行した際に、解放処理として呼ばれるメソッドである。 よってここではメモリの解放処理を書く必要があるので、free()を使っている。これにより、ガーベジ コレクションが行われるタイミングで、Java の REGION インスタンスが必要でなければメモリも同時 に解放されるので、プラグイン製作者は確かにメモリ管理の必要がなくなっていることが分かる。 それ以外の関数はそれぞれの Getter、Setter に対応している。まず long 値から REGION ポイン タに変換してから値の読み書きを行っていることが分かる。しかし一方で、ポインタが示す値にアク セスするたびにこれらの Getter、Setter を使わなければならないため、これがオーバーヘッドとな ることが予想される。 これらのコードにより、Java のコードでは long 値を保持するだけで、ポインタを扱うことができてい ることが分かる。 27 5.2. 具体例の実装 Beryl が提供する API は、beryl.h に記載されている。その個数は以下の通りである。 関数またはグローバル変数 3525 個 構造体または共用体 279 個 Beryl が独自に定義しているものだけでなく、OpenGL 拡張機能や、関連する API のヘッダーも 間接的にインクルードされるためこのような膨大な数になる。 この全てを手作業でラッピングすることは非常に困難であるため、本研究の目的に応じ、本研究 のミドルウェア実装手法が有効であることを確認するため、既に Beryl に搭載されているプラグイン の一部をミドルウェアによって再実装し、その有効性を検証することにする。 その対象として Screenshot プラグインを採用する。Screenshot プラグインはマウスで指定した範 囲を PNG ファイルとしてスクリーンショットを撮るプラグインである。 再実装した Screenshot に対して、後述の「評価」を適用することにする。 28 5.2.1. 再実装の対象関数 再実装の対象となる関数は以下のような関数である。 static void shotHandleMotionEvent(CompScreen * s, int xRoot, int yRoot) { SHOT_SCREEN(s); if (ss->grabIndex) { REGION reg; reg.rects = ®.extents; reg.numRects = 1; reg.extents.x1 = MIN(ss->x1, ss->x2) - 1; reg.extents.y1 = MIN(ss->y1, ss->y2) - 1; reg.extents.x2 = MAX(ss->x1, ss->x2) + 1; reg.extents.y2 = MAX(ss->y1, ss->y2) + 1; damageScreenRegion(s, ®); ss->x2 = xRoot; ss->y2 = yRoot; reg.extents.x1 = MIN(ss->x1, ss->x2) - 1; reg.extents.y1 = MIN(ss->y1, ss->y2) - 1; reg.extents.x2 = MAX(ss->x1, ss->x2) + 1; reg.extents.y2 = MAX(ss->y1, ss->y2) + 1; damageScreenRegion(s, ®); damageScreen(s); } } この関数はマウスによってドラッグされた際のイベントを処理している。このコードからラッピング すべき対象の構造体と、関数をまとめると以下の様になる。 29 構造体 CompScreen ShotScreen REGION BOX 関数 damageScreenRegion damageScreen またint型の変数はラッピングする必要がない。Java のプリミティブとして用意されているためであ る。 5.2.2. 再実装時の Java 文法に適した形への変換 必要な API がラッピングされれば変換が開始できる。しかし、単純に変換できない場合がある。 Screenshot の例では次の箇所が問題になる。 if (ss->grabIndex) この条件式評価は grabIndex が int 型であるため、Java ではコンパイルエラーとなってしまう。 よって次のように書き換えなければならない。 if (0 != ss.getGrabIndex()) 30 5.2.3. 呼出コストの削減 ネイティブコードではポインタが示す先へのアクセスはローコストだが、Java プラグラムが API に よってラッピングされたデータに毎回アクセスすることはコストが大きい。よって一時変数をキャッシュ として用いる。 5.2.4. ローカルに割り当てたオブジェクトの扱い スタックに積まれるローカル変数は、Java で扱えるプリミティブ型であれば、C と同様にコーディ ングできるが、構造体などラッピング API を通じてのみ操作できるものは扱うことができない。なぜ ならば、ラッピング先でローカル変数としてしまうと、ラッピングしている関数のスタックに乗っている ので、Java プログラムにリターンすれば消えてしまうからである。 よって、それを回避するために、構造体は動的にメモリを確保しポインタで扱うようにする。ローカ ル変数の場合メモリ管理は必要ないが、動的に確保した場合にはメモリ管理が必要になる。そこ で JVM のガーベジコレクションを利用することで、メモリ管理を自動化するようにする。 ここでは REGION がその例となる。 31 5.2.5. 再実装されたユーザプラグイン 上記のような変換方法を用いて、再実装された ScreenShot の Java コードを以下に示す。 public void HandleMotionEvent(final CompScreen s, final int xRoot, final int yRoot) { final ShotScreen ss = new ShotScreen(s.getValue(), this.displayPrivateIndex); if (0 != ss.getGrabIndex()) { final REGION reg = new REGION(); reg.setRects(reg.getExtentsPointer()); reg.setNumRects(1); final BOX extents = new BOX(reg.getExtentsPointer()); int ss_x1 = ss.getX1(); int ss_y1 = ss.getY1(); int ss_x2 = ss.getX2(); int ss_y2 = ss.getY2(); extents.setX1(API.MIN(ss_x1, ss_x2) - 1); extents.setY1(API.MIN(ss_y1, ss_y2) - 1); extents.setX2(API.MAX(ss_x1, ss_x2) + 1); extents.setY2(API.MAX(ss_y1, ss_y2) + 1); API.damageScreenRegion(s.getValue(), reg.getValue()); ss.setX2(xRoot); ss.setY2(yRoot); ss_x2 = xRoot; ss_y2 = yRoot; extents.setX1(API.MIN(ss_x1, ss_x2) - 1); extents.setY1(API.MIN(ss_y1, ss_y2) - 1); extents.setX2(API.MAX(ss_x1, ss_x2) + 1); extents.setY2(API.MAX(ss_y1, ss_y2) + 1); API.damageScreenRegion(s.getValue(), reg.getValue()); API.damageScreen(s.getValue()); } } 32 6. 評価 次の三つの視点から、本研究を評価する。 また評価に用いた計算機環境は以下の通りである。 CPU Intel Celeron 2.4GHz Memory 512MB GPU Geforce FX 5700(Driver version: 9629) OS Ubuntu Linux 6.10 JDK 6 Beryl 0.1.3 6.1. コーディング コーディングレベルでの評価を行う。コード量と可読性について、ネイティブコードと Java コード を比較する。 コード量は Java の方が多いが、これは Setter、Getter に代表される記法の差、また前出の呼出 コスト削減のため一時変数をおいているためであり、ロジックという点では同一である。 一方で、ローカル変数で構造体は使用できないが、自動化されたメモリ管理によりポインタを扱っ てもコーディング量は変化していない。 よってコーディングレベルのおいて本手法を用いてもマイナス要因となるこはないと分かる。一方 で、メモリ管理が自動化されているため、より多くのデータを扱う場面ではコーディング量が減ると 予想できる。また量だけではなく、ガーベジコレクションの自動化によりメモリリークが発生しにくい ので、パフォーマンスの向上も期待できる。 6.2. 動作 次にプラグインの動作についての評価する。正しくプラグインが動作するか、CPU 占有率、メモリ 使用量について、ネイティブコードと Java コードを比較する。 両者を使用してみると正しく動作し、また該当イベント処理時の CPU 負荷は非常に軽微である 33 ため体感できるほどの違いがないことが分かる。より客観的に違いを検証するため次の二点につ いて評価した。 6.2.1. 初期化時 プラグインの初期化時には、JVM の起動、クラスファイルのロードなどがあるため、大きく処理に 差がある。そこで CPU 使用率と、メモリ使用率を調べたところ次の様になった。 図10 プラグイン起動時の CPU 負荷率 1. オリジナル版 Screenshot 起動 2. オリジナル版 Screenshot 終了 3. Jadeite 版 Screenshot 起動 4. Jadeite 版 Screenshot 終了 メインメモリの変化 起動前 起動後 差 オリジナル版 Screenshot 207MB 208MB 1MB Jadeite 版 Screenshot 207MB 211MB 4MB よって、共に Jadeite 版のほうが多くのリソースを使用していることが分かる。だがこの負荷は起動 時だけである。ログインしている限り、この起動処理は一度しか行われないので、大きな問題には ならないと考えられる。 34 6.2.2. データの読み書き ラッピングされた構造体に対して、メモリ確保、書き込み、読み込み、メモリ解放を大量に行いラッ ピングによる遅延を評価する。 評価に用いたコードを以下に示す。 public class Timing { final static int AAA = 1000; final static int BBB = 1000*1000; public void doSomething() { for (int i = 0; i < AAA; ++i) { final REGION r = new REGION(); for (int j = 0; j < BBB; ++j) { r.setRects(0); r.getExtentsPointer(); } } } public static void main(final String[] args) { long start = System.currentTimeMillis(); Timing timing = new Timing(); timing.doSomething(); long stop = System.currentTimeMillis(); System.out.println("Time: " + (stop - start) + "msec"); } } 35 #include <time.h> #include <sys/time.h> #include <sys/resource.h> #include <stdio.h> #include <beryl.h> double getrusage_sec() { struct rusage t; struct timeval tv; getrusage(RUSAGE_SELF, &t); tv = t.ru_utime; return tv.tv_sec + (double)tv.tv_usec*1e-6; } int AAA = 1000; int BBB = 1000*1000; void doSomething() { int i, j; for (i = 0; i < AAA; ++i) { REGION* r = (REGION*)malloc(sizeof(REGION)); for (j = 0; j < BBB; ++j) { r->rects = 0; r->extents; } free(r); } } int main() { int i; int n = 0; double t1,t2; t1 = getrusage_sec(); doSomething(); t2 = getrusage_sec(); printf("time = %10.70f\n", t2 - t1); return 0; } 36 これによる計測結果は以下の通りである。 Java 約 131 秒 C 約3秒 上記のように大きく差が出たのは、JNI の呼出コストによるものである。Java だけで閉じたコードで ほぼ同じ内容のコードを書くと、その結果は約3秒となり、C と同じである。 よって極端にラッピングされた構造体へのアクセスがある場合には、大きく性能を損なうことが分 かった。 6.3. 網羅性 本研究が提案するミドルウェアは、Beryl Plugin が使用できる API の全体のうち、どれくらいをラッ ピングできたのかを評価する。 前出の「具体例の実装」でラッピングすべき対象は、3804 項目であることが分かっている。本研 究でラッピングできたのは、8 件。よってカバーできている範囲は、全体の0.2%である。 本研究の目的は、ミドルウェアの実装方法の検証にあるので、網羅性がある必要はないがミドル ウェアとしてリリースするためにはこのほとんどをカバーする必要がある。本研究ではひとつひとつ を手動で実装している。だが、もし全体をラッピングするのであれば、自動化する必要があるだろう。 37 7. 結論 7.1. 考察 まずこの方法が有効であったかどうかを考察する。各評価から以下の点がまとめられる。 ● 関数ポインタなど難易度が高いコーディングを隠蔽することはできた。 ● メモリ管理を自動化することができた。 ● 呼出コスト分の負荷は免れない。 ● 大量の API をすべて手動でラッピングすることは無理がある。 よって得られる結論は、必要な API のラッピングが行うことができれば、この方法によりプラグイン を作ることは可能であるということである。 7.2. 今後の課題 今後の課題として、このミドルウェアを実用するとした場合には、まずラッピング自動化が必要と なる。それには C 言語の文法を解釈し、適切に変換しなければならない。単純な文字列処理では 対応することはできない。マクロの展開や、ポインタの隠蔽が必要となるからである。 しかしその場合にも、本研究で手動で行ったように、例えば REGION クラスのコンストラクタから malloc を実行するが、CompScreen のコンストラクタはポインタを外部から受け取るなど意味を理解 した対応をさせることは非常に難しい。 同様にキャストの問題がある。Screenshot の例でいうと、void*型で Beryl から与えられるポインタ を内部で定義した構造体にキャストしている部分がある。これは定義からは知る由もなく、プラグラ マが構造を知っているからこそできたキャストである。これは自動化することはできない。この問題 は関数ポインタのキャストにも波及する。 ミドルウェアとして完成されたものとするには、こういった課題をクリアする必要がある。 38 8. 謝辞 本研究は、電気通信大学情報通信工学科情報システム学講座の寺田実助教授の御指導の下 で行われたものです。寺田助教授には、研究全般に渡って適切な御意見と御指導を賜わり、私の 研究を温かく見守って頂きました。完成に至る事ができましたのは、何よりも寺田助教授の御指導 によるものです。心からお礼を申し上げます。 総合情報処理センター助手の丸山一貴様には、本研究の御指導を頂き、本当にありがとうござ いました。 さらに、同じ研究室に所属している大学院の皆様、並びに同期の皆様方のおかげを持ちまして、 この一年間を有意義に過ごせたのだと感じております。 最後に、この一年間御協力していただいた全ての皆様に深く感謝し、謝辞とさせて頂きます。本 当にありがとうございました。 39 9. 参考文献 ● Beryl-project.org http://www.beryl-project.org/ ● lg3d: LG3D Core Project https://lg3d.dev.java.net/ ● Microsoft Windows Vista: Windows Aero http://www.microsoft.com/japan/windowsvista/experiences/aero.mspx ● Java 言語 – Wikipedia http://ja.wikipedia.org/wiki/Java ● Java Native Interface – Wikipedia http://ja.wikipedia.org/wiki/JNI 40 10. 付録 41