2008-08-31

jrunscriptでGUIスクリプトを起動してみて気づいたこと

  1. jrunscriptのバッチモード(-fオプション)でAWTやSwingのウィンドウを表示するスクリプトファイルを起動した場合、ウィンドウが一瞬だけ表示されて終了してしまう。
  2. スクリプトの最後にjava.lang.Thread.currentThread().sleep(1000);を付け足すと、スレッドを待機させている間はウィンドウが表示された。
  3. どうやらスクリプトを処理するメインスレッドが、スクリプトを読み終えた時点で(GUIのスレッドが生きていても)exitしているように見える。
  4. そこで、wait-notifyの組合せを使って、ウィンドウがクローズされるまでメインスレッドを待機させることを思い付いた。
  5. Rhinoで同期処理を上手くやる方法はあったっけ?
  6. "バッチモード"という位なので、これはこれで正しいのかもしれない、と勝手に自分を納得させる。
  7. あえなく完。
  8. Rhino組み込みのsync関数に気付き、sync(function() {this.wait();})の返す同期化された関数を使ってみる。
  9. wait関数が見つからないとか言われた...。確かにtypeof this.waitはundefinedだった。
  10. やっぱり完。
  11. スクリプトの最後に while (frame.isDisplayable()) {java.lang.Thread.currentThread().sleep(1000);} を付けたところ、GUI側でウィンドウクローズ時にちゃんとdispose()してれば想定通りに動く。
  12. 何か納得いかないものの完。
ちなみに、対話モードでload('GUIスクリプトファイル名');を使うなら問題なし。

イベントアダプタをJava Scripting EngineのJavaScriptで真似する

Java1.6付属のRhinoでJavaインタフェースを実装する場合、以下のように書ける(jrunscriptで実行)。
js> var runner = new java.lang.Runnable() { run: function() { print(this); }};
js> new java.lang.Thread(runner).start();
js> [object Object]
問題は、GUIのイベントリスナを書こうとした場合、使いもしないメソッドまで記述することになるので面倒くさい。
Javaで実装する場合はアダプタクラスがあるのだけれど、アダプタクラスはインタフェースではなく抽象クラスなので、下記のようなスクリプトは通らない。
// 変数frameにはjava.awt.Frameが入っているものとする
frame.addWindowListener(new java.awt.event.WindowAdapter() {
    windowClosing: function(evt) {
        java.lang.System.exit(0);
    }
});
// 上記構文はインタフェース用なので、抽象クラスに適用できない
以下のスクリプトは一応通るものの、実装しないでおいたイベントハンドラが後で呼ばれると、java.lang.NoSuchMethodExceptionが発生する。
frame.addWindowListener(new java.awt.event.WindowListener() {
    windowClosing: function(evt) {
        java.lang.System.exit(0);
    }
});
// この後、frameを表示するとwindowActivatedやwindowOpenedが未実装なので例外が発生する
そこで、必要なイベントハンドラの連想配列を渡すとWindowListener(を実装したクラスのインスタンス)を作成して返す関数を書いてみた。イベントハンドラごとにベタベタ似たような処理を書いている箇所がもっとスマートにならないものか...。
// 渡された連想配列からWindowListenerを作成する関数
function createWindowListener(tbl) {
    function ignoreEvent(evt) {}
    function getValue(value, defaultValue) { return value ? value : defaultValue; }
    return new java.awt.event.WindowListener() {
        windowActivated: getValue(tbl['windowActivated'], ignoreEvent),
        windowClosed: getValue(tbl['windowClosed'], ignoreEvent),
        windowClosing: getValue(tbl['windowClosing'], ignoreEvent),
        windowDeactivated: getValue(tbl['windowDeactivated'], ignoreEvent),
        windowDeiconified: getValue(tbl['windowDeiconified'], ignoreEvent),
        windowGainedFocus: getValue(tbl['windowGainedFocus'], ignoreEvent),
        windowIconified: getValue(tbl['windowIconified'], ignoreEvent),
        windowLostFocus: getValue(tbl['windowLostFocus'], ignoreEvent),
        windowOpened: getValue(tbl['windowOpened'], ignoreEvent),
        windowStateChanged: getValue(tbl['windowStateChanged'], ignoreEvent)
    };
}

// 使ってみる(ウィンドウが表示されて、閉じるボタンを押すと終了する)
// jrunscriptから実行する場合は、load関数で読み込ませないと一瞬で終了する
var frame = new java.awt.Frame("Test Frame");
var panel = new java.awt.Panel();
panel.setPreferredSize(new java.awt.Dimension(320, 240));
frame.add(panel);
var handlers = { windowClosing: function(evt) {
    var window = evt.getSource();
    window.setVisible(false);
    window.dispose();
    //java.lang.System.exit(0); 最近のJVMでは不要になったらしい
} };
frame.addWindowListener(createWindowListener(handlers));
frame.pack();
frame.setVisible(true);

2008-08-23

LCC-Win32が自由にダウンロードできた頃

私がまだコマンドラインで何かすることさえ思いもよらないプログラミング初心者だった頃、LCC-Win32が手元にある唯一のグラフィカルな統合開発環境だった。
プログラミングに必要なツールが一通り揃っているのはもちろん、C言語チュートリアルやWin32SDKのヘルプ(これらは単独でも役に立つ)、それに、LCC-Win32でビルドできるサンプルソースを公開しているWebサイトもあるので、(ちょっと英語が読めれば)初心者でも手元に何も無い状態から始められるという点で画期的だった。
特にC言語チュートリアルは素晴らしかった。そこには開発者であるJacob Navia氏の意見があり、C言語や"昨日今日の言語"、メモリ管理と自動ガーベジコレクタに関する(今では古いかもしれない)熱い主張を見ることができた。当時の私が難解なWin32向けC言語プログラミングを続けられたのは、このチュートリアルが与えてくれた情熱のお陰なのだと、今でも思っている。
一方で当時ちょっと不安に感じたのは、「LCC-Win32で作ったソフトウェアを売って良いか」について、なぜかライセンス条項に明確な記述がなかったことだった(作ったソフトウェアの"著作権"が作成者にある、という記述はあったはず)。
私が自作したDLLを商用利用も可能な条件でユーザに配布したいと思った時に、どうしてもこの問題が引っ掛かり、あれこれ調べた末、最終的にはMinGWに乗り換えた。
あれから5年以上経った今、LCC-Win32のWebサイトのトップページには「License: 」が加わり、商用利用に関する細かい記述が並ぶ。閲覧者が何かダウンロードしたければ会員登録が必要で、それが無料だとしても、私は何もかも最初から自由にダウンロードできた当時と比べてしまい、どこか違和感を感じる。

2008-08-09

モノクロアニメをdrawLine()で描画する

「へらへらアニメ」を表示するアプレットを作ろうとした時に念頭にあったのは、できるだけ沢山のパソコン上で楽しめるようにしたいということで、当時はWin9x+MSJavaという組合せも考慮する必要があった。これは、Java1.1.x互換なコードを書く必要があることを意味する。
AWTの解説書やWebサイトで調べてみると、ビットやバイトで構成されたイメージでアニメーションをやりたい場合、MemoryImageSourceを使うのが手堅いやり方のようだった。さらにJava APIのドキュメントを読んでみて、MemoryImageSourceでやるしかないのだと確信した。
そこで、実際にアプレットを作って動かしてみた。

なんか変だ。

アニメーション自体の速度がGIFアニメよりも若干まずい点は、まあ、そういうものだと諦めるとしても、定期的に極端に低速になるというか、何かがロックされているかのごとくカクカクした動きになることがあって、このままだとソフトウェアの不具合だとみなされてしまいかねない動きだったので、さすがに公開はしなかった。
注意しておくと、AWTの解説書もWebサイトもJava APIドキュメントも、全く落ち度はない。異なるスペックのパソコンを借りたりして調べた限りでは、どうもWin9xがプリインストールされたマシンをそのまま使い続けているような古い環境だと上記の問題が起こるようだった。
困ったことに、調べたり人に聞いたりしてもこの問題の原因は分からなかった。
あれこれ変な方法を試したり失敗したりを繰り返した末に、一番あり得ない手段だと思っていた、drawLine()で左から右へ線を何度も引くやり方でイメージを描画することにした。すると、確かに速度がまずくなる瞬間は発生するものの、MemoryImageSourceを使った時よりはずっとマシになっていた。
結局、drawLine()で描画する方を公開することにした。それから忙しくなったりパソコンが変わったりしているうちに、MemoryImageSourceを使った方のソースはどこかに行ってしまった。
現在公開されている「へらへらアニメ」用のアプレットはdrawLine()で描画されていて、今でも問題なく動いている。が、いまだにこのやり方が本当に良かったのかどうか疑問に思うことがある。

2008-08-03

文字エンコーディング名かCharsetか

Javaでテキスト処理をする時に、
Reader createReader(String fileName, String encoding)
なメソッドを書いていて思った。
最近はjava.nio.charset.Charsetがあるので、以下のようにも書ける。
Reader createReader(String fileName, Charset charset)
後者はちゃんと目的に特化したクラスを使っているので、前者よりも手堅い印象がある。特に、文字エンコーディング名がCharsetに変換される時の例外処理をcreateReader()が抱えなくて済むという安心感がある。
一方で、createReader()を使う側としては、前者のオーバーライドも用意しておいてくれた方が便利でうれしい。

似たような話は、以下のような場合にも出てくる。
  • ファイルパスかjava.io.Fileか
  • URLかjava.net.URLか(URIの場合も同様)
実装する前にちょっと考えないと、チェック処理の責任が色んなメソッドに波及して面倒なんだよね...。

いつの間に文字列は汎用データ型みたいになったんだろう。
Win32 APIをC言語から利用する場合、32ビット整数が色んなオブジェクトやら何やらを表していて(ハンドルとかいった)、持ち運び易い反面、危なっかしいところがあった。(Win32 APIを使うスクリプト言語まで同様のやり方でリソース管理するようユーザに要求する流れが現れたことで、問題はさらにややこしくなる...)

2008-08-02

MinGWのサイトがリニューアル中

Win9X向けのソフトを作り始めた頃から、大変お世話になっているサイト。
久しぶりに覗いてみたら、ど真ん中にMySQLのエラーメッセージが...。
8/3 追記: 直ってた。

MinGW
http://www.mingw.org/