2010/12/21

テキストファイルの文字コードを判別して読み込む

不特定多数のテキストファイルから検索を行ったりするスクリプトを作る場合、文字コードを正しく認識して読み込む必要があります。
NILScriptのFileオブジェクトに用意されているload()メソッドでは、文字コードを指定するための第1引数を省略した場合、エラーにならずに読み込める文字コードを自動選択して読み込んでくれますが、誤った文字コードでも全てのバイトをデコードできる場合などは、誤認識してしまうことがあります。
より正確な文字コード認識を行いたい場合、「Charset」というユニットを使います。
以下のように記述すれば、Charsetユニットによる高精度な文字コード判別を行ってテキストファイルを読み込むことが出来ます。
var text=require('Charset').Detector.load(someFile);

また、以下のようにすることで、検出した文字コード名を得ることも可能です。
var charset=require('Charset').Detector.detect(someFile);

他にも、ストリームオブジェクトから読み込みを行いながら文字コードを判別する機能なども用意されています。

Charsetユニットでは、IEなどで使用されているmlang.dllの機能を利用して文字コードの判別を行っています。「です」「である」などの頻出文字列の出現なども手がかりにしているらしく、通常のload()で使用している方法より正確に判別してくれます。

なお、読み込まれた文字列はUnicode文字列として保持されるので、海外製の処理系にありがちな駄目文字問題に悩まされることはありません。

2010/12/13

入出力のコンソールを子プロセスと共有して進捗などを表示

NILScriptは、コマンドラインで指定された処理を行って直ちに終了するコンソールプログラムを組み合わせた自動処理の実現にも役立ちます。

動画変換やダウンロードなど時間のかかる処理を行うプログラムの多くは、標準出力で進捗状況を確認できるようになっていますが、作業中にバックグラウンドで自動処理させたい場合には、子プロセスが起動するたびにコンソールウィンドウが表示されては邪魔になってしまいます。
しかし、完全に出力を非表示にしてしまうと、残り所要時間が把握しづらく不便です。

スクリプトのプロセスが所有するコンソールに子プロセスの出力を表示するようにすれば、いちいち新たなウィンドウを表示せずに進捗状況を確認できるようになって便利です。
NILScriptのrun()やProcess.create()では、以下のようにオプションオブジェクトに「shareConsole:true」を指定する事で、子プロセスと現在のプロセスのコンソールを共有させられるので、簡単に子プロセスの出力を自分のコンソールに表示させられます。

println("1/3")
run('"'+app1+'" "'+file+'"',{shareConsole:true}).waitExit();
println("2/3")
run('"'+app2+'" "'+file+'"',{shareConsole:true}).waitExit();
println("3/3")
run('"'+app3+'" "'+file+'"',{shareConsole:true}).waitExit();
println("done");

2010/12/09

Webページのスクリーンショット画像を生成

Imageユニットが提供する画像処理機能には、ウィンドウの表示内容を画像として取得するcapture()メソッドなども用意されています。
これをATLユニットで用意されているIEコンポーネントの表示機能と組み合わせれば、以下のようにしてWebページのスクリーンショットを生成することも可能です。
use('Window','ATL');
var w=OnScreenDisplay.create({
    width:800,height:600, x:-32000,alpha:0,
    children:{
        ie:{
            type:Trident,
            top:0,bottom:0,left:0,right:0,
        }
    }
}).show(true);
w.request(function(){
    this.ie.object.observe('DWebBrowserEvents2_DocumentComplete',function(o){
        w.capture().save('webpage.png');
        w.close();
    });
    this.ie.object.navigate("http://lukewarm.s151.xrea.com/nilscript.html");
});
w.wait('destroy');
配下にIEコンポーネントを配置したOnScreenDisplayを生成し表示させた後、ウィンドウのスクリーンショットを保存しウィンドウを閉じるというイベントハンドラを、IEコンポーネントのCOMオブジェクトの読み込み完了イベントに割り当て、キャプチャしたいページのURLにnavigate()しています。

OnScreenDisplayは、Windowクラスを継承して定義されているクラスで、タイトルバーや枠などを持たず、タスクバーにも表示されず、クリックが背後のウィンドウに透過する情報表示向けのウィンドウです。
これをx:-32000という絶対に画面に表示されない位置に表示させることで、一切画面に表示されることなくキャプチャを実行させられます。
通常のウィンドウは、描画先としてデスクトップのデバイスコンテキストを使用しているため、実際に表示されていない部分は真っ黒になってしまいますが、alphaオプションで半透明にしたウィンドウは独自のデバイスコンテキストを持つため、画面外にあってもキャプチャ可能になります。
半透明にしておけば画面外に出す必要は無い気がしますが、表示される瞬間に一瞬だけちらつきが見えてしまうので、x:-32000は有った方がいいでしょう。

任意のWebページをIEコンポーネントに読み込ませて処理する

NILScriptのCOMユニットやATLユニットを使えば、IEコンポーネントでWebページを表示し、様々な操作を行うことも可能です。
しかし、NILScript上のCOMオブジェクトは明示的にfree()メソッドを呼び出して解放しなければならないため、多数のDOMノードオブジェクトを使用する処理などは記述が面倒です。

そこでおすすめなのが、HTTPD機能で生成した動的WebページをIEコンポーネント上に表示し、Webページ側のJavaScriptで処理を行わせる方法です。
HTTPDには、Webページ上のJavaScriptとNILScript側の関数を連携させるためのclientSide関数とserverSide関数という機能が用意されています。
clientSide関数はブラウザ上で実行されるため、DOMオブジェクトの明示的開放が要らないだけでなく、jQueryオブジェクトも利用できるので、複雑なDOM操作も手軽に記述可能です。

任意のURLをIE上に読み込んでDOMを利用して情報の取得などを行いたい場合には、HTTPD上に用意したWebページのように自由にスクリプトを埋め込んで実行させることは出来ませんが、ブックマークレット機能を利用することで同様のことが行なえます。
ブックマークレットとは、「javascript:」に続いてスクリプトを記述したURIをブックマークなどから開くことで、Webページ上で任意の処理を実行させられるという手法のことです。
NILScriptのHTTPDでは、NILScript上で定義したclientSide/serverSide関数を利用した処理をブックマークレットとして呼び出せるようにする機能が用意されています。
このブックマークレットのURLをIEコンポーネントのnavigate()メソッドで読み込ませれば、表示しているWebページ上でNILScript上の機能を利用した任意の処理を実行させられます。

以下は、この手法の利用してWebページのbody要素の高さを調べて表示するという例です。
use('Window','ATL','HTTPD');
var result;
var httpd=HTTPD.create({
    serverSide:{
        report:function(value){
            //受取った解析結果を変数に代入し、ウィンドウを閉じる(4)
            result=value;
            w.close();
        },
    },
    bookmarklets:{
        analyze:function(){
            //ブラウザ上でWebページを解析し、結果をserverSide関数に渡す(3)
            report([$(document.body).height()]);
        },
    },
});
var w=PlainWindow.create({
    width:800,height:600,
    children:{
        ie:{
            type:Trident,
            top:0,bottom:0,left:0,right:0,
        }
    }
});
w.request(function(){
    this.ie.object.observe('DWebBrowserEvents2_DocumentComplete',function(o){
        //読み込みが完了したらブックマークレットを読み込ませる(2)
        this.navigate(httpd.bookmarkletURL('analyze'));
    });
    //解析したいWebページを読み込ませる(1)
    this.ie.object.navigate("http://lukewarm.s151.xrea.com/nilscript.html");
});
//ウィンドウが閉じられたら、解析結果を利用した処理を行う(5)
w.wait('destroy');
println(result);
HTTPD上では、analyzeというブックマークレット関数とreport()というserverSide関数を定義しています。
ブックマークレット上では、「$」でjQueryオブジェクトが利用できる他、serverSideで定義した関数の呼び出しが可能です。
serverSide関数は、第1引数に関数に渡す引数の配列、第2引数に返り値を受取るコールバック関数(省略可)を指定するという特殊な呼び出し方になっていることに注意してください。
また、bookmarkletsの関数は一旦文字列化されてブラウザ上で実行されるため、NILScript側の変数を直接参照することは出来ず、ブラウザのスクリプトエンジンがサポートしていない構文も利用できないことにも注意が必要です。

次に、Tridentコントロールを一つだけ配置したPlainWindowオブジェクトを生成します。
そして、このIEコンポーネントのCOMオブジェクトのobserve()で、Webページの読み込み完了時にブックマークレットの実行を行うというイベントハンドラを登録し、さらに調査したいURLを読み込ませます。
この処理は、ウィンドウの初期化が完了される前に実行されてしまわないように、ウィンドウを所有しているスレッド上で処理を実行させるrequest()メソッドを利用して呼び出します。
Webページの読み込みが行われると、スクリプト中のコメントの後ろに付けられた番号の順番で処理が実行されていき、最終的に調査結果の表示が行われます。

2010/12/08

NILScriptのプロセス自身のメモリ使用量を取得する

大量のデータを処理するスクリプトを作っていてメモリ使用量が心配になったときや、メモリの解放漏れが無いか確かめたいときなどには、スクリプトのプロセスのメモリ使用状況を調べたくなります。
そんなときいちいちタスクマネージャでスクリプトのプロセスを探してメモリ使用量を調べるのは面倒です。

NILScriptのProcessオブジェクトには、プロセスのメモリ使用状況を取得する機能があり、「Main.process」で現在スクリプトを実行しているプロセスをProcessオブジェクトとして得ることができるので、自プロセスのメモリ使用状況を簡単に取得できます。

例えば、以下のようにすれば、Imageオブジェクトを生成して解放したときにメモリの解放漏れがあるかどうかが分かります。
use('Image');
for(var i=0;i<100;i++){
    try{
        var img=Image.create(1920,1080);
    }finally{
        img.free();
    }
}
println(Main.process.pagefileUsage);

なお、Imageオブジェクトのfree()が正しく全てのリソースを解放していなかった件については、先ほど修正しました。

2010/11/21

SQLiteのFTSを応用して長文の要点を抜き出してみる

SQLiteの全文検索機能であるFTS3には、snippet()という関数が用意されており、検索ワードが含まれていた箇所の前後の文章を取得できます。
これを応用すれば、長文から要点っぽい部分を抜き出す機能が実現できるのではないかと思い、Segmenterで分かち書きしたテキストをDBに格納し、出現頻度上位の単語を検索した時のsnippet()を取得するというスクリプトを作成してみました。
use('Clipboard','Segmenter','SQLite');
try{
    var seg=Segmenter.create("ja");
    var db=DB.open();
    db.execute("CREATE VIRTUAL TABLE docs USING fts3(body)");
    var a=seg.segment(Clipboard.text.replace(/[\s\r\n\t]+/g,' '));
    db.table('docs').insert([{body:a.join(' ')}]);
    var re=/[ぁ-ん]{4,}|[々〇\u303B\u3400-\u9FFF\uF900-\uFAFF0-90-9a-za-zァ-ヴーア-ン゙ー]{2,}/i;
    var k=$G(a).groupBy(void(0),function(w,g)({w:w,c:g.count()}))
               .filter(function(o)(re.test(o.w)))
               .orderByDesc('c').map('w').head(3).toArray();
    println(k);
    println(db.select(
        [{d:'snippet(docs,"<<",">>","...",0,64)'},'docs','body MATCH $q'],
        "",-1,0,{q:k.join(' ')}
    ).first().d.replace(/([^\w])\s|\s([^\w])/g,'$1$2'));
}finally{
    free(seg,db);
}
最初にSegmenterオブジェクトとオンメモリDBオブジェクトを生成し、クリップボードのテキストを分かち書きした配列をDBに格納します。
次に、ジェネレータの拡張メソッドで分かち書き配列を加工し、出現頻度の高い上位3件の単語を得ます。
この時、記号や助詞などの不要な単語を除外するため、正規表現で大雑把にフィルタリングしています。
そして、その頻出単語をキーワードとしてDBから検索を行い、sinppet()を取得し、不要な空白を除去して出力します。
なお、snippet()の引数ではテーブル名、マッチ箇所を囲む開始・終了文字列、列番号(0~)、抜き出したい単語数を指定できます。単語数を負の数にすると、抜き出す箇所が複数に分かれる場合に、合計ではなくそれぞれの部分の単語数が指定した数になるように抜き出されるようです。

適当にいくつかのニュースやブログ記事で試してみたところ、期待通りに頻出単語とそれを含む部分を抜き出せました。
文章によっては本題ではない部分が抜き出されてしまうこともありましたが、文の書き出しなどを決め撃ちで抜き出す単純な方法よりは、有用な部分を抜き出せる可能性が高いような気がします。

2010/11/19

Segmenterで分かち書きしたテキストをSQLiteで全文検索

NILScriptには軽量データベースエンジンSQLiteを扱うクラスが用意されています。
SQLiteには文字列中の単語をインデックス化して高速な全文検索を行うFTS3という機能が用意されていますが、標準搭載されている単語分割処理は文字列を空白文字で分割するだけの簡易的なものなので、そのままでは日本語のテキストをまともに処理できません。

そこで、日本語の文字列を分かち書きする機能を提供する「Segmenter」クラスを新たに用意しました。
アルゴリズム本体はプラグイン方式で追加できるようになっており、現在はTinySegmenter- Javascriptだけで実装されたコンパクトな分かち書きソフトウェア高速化のための修正を加えたものにSpiderMonkey独自機能を利用したチューニングを加えて作成したプラグインが用意されています。
TinySegmenterは簡易的な物なので、「すもももももももものうち」のように平仮名が連続していたりすると「すも | も | も | もも | も | ももの | うち」のようなおかしな結果になってしまうことがありますが、検索に利用するだけなら十分でしょう。(検索語句の「すもも」も「すも | も」として検索されるため)
より正確な分かち書きが必要な場合は、MeCabなどのエンジンを利用したプラグインを作成するとよいでしょう。

以下のスクリプトは、SegmenterとSQLiteを組み合わせて全文検索を行うサンプルです。
var segmenter=require('Segmenter').Segmenter.create("ja");
var db=require('SQLite').DB.open();
db.execute("CREATE VIRTUAL TABLE docs USING fts3(title TEXT, body TEXT)");
db.begin();
Main.directory.directory('doc').files.execute(function(f){
    db.table('docs').insert([{title:f.baseName,body:segmenter.segment(f.load()).join(' ')}]);
});
db.end();

var q;
while(q=prompt("search query","")){
    println("search result for: "+q);
    db.select([,'docs','body MATCH $q'],"title",-1,0,{q:'"'+segmenter.segment(q).join(' ')+'"'}).execute(function(o){
        println(o.title);
    });
}
free(db,segmenter);
各クラスの基本的な使い方については、同梱のドキュメントなどを参照してください。

全文検索型のテーブルを作成するには、「"CREATE VIRTUAL TABLE name USING fts3(colnames)」のようにします。
そして、挿入する値を指定する際に、分かち書きした値を指定します。Segmenterオブジェクトのsegment()メソッドは配列を返すので、空白で連結する必要があります。

検索時にMATCH演算子を使うことで、インデックスを利用した全文検索を行なえます。
検索語句は挿入時と同様にSegmenterで分割して空白区切りにし、前後に「"」を付加する必要があります。
MATCH演算子の詳しい使い方はSQLite FTS3 Extensionなどを参照してください。

上記のスクリプトを実行すると、キーワード入力用のプロンプトが表示され、入力した語句を含むドキュメントが列挙されます。
プロンプト表示までに発生する2秒ほどの待ち時間は、主にSegmenterの処理による物のようです。
個人利用で扱う程度のテキスト量なら、このくらいの処理速度で十分実用に耐えられるでしょう。

2010/10/27

設定ファイルにウィンドウ位置などの情報を保存する

ウィンドウを表示して入力の受け付けや情報の表示を行うタイプのプログラムでは、ウィンドウを表示する際、前回のウィンドウの位置・サイズを記憶して再現してくれるようにすると便利です。

このような設定情報の保存を行う際には、pref()関数を使います。
この関数は、適切なユーザー設定ディレクトリを自動認識して、設定ファイルを表すFile/Directoryオブジェクトを返してくれます。

設定ファイルの形式は、JavaScriptのオブジェクトリテラルの表記と同じ書式のテキストで保存するJSONという形式を使うと良いでしょう。
FileオブジェクトのloadJSON()やsaveJSON()というメソッドを使えば、簡単にオブジェクトとして読み込んだり保存できます。

下記の例は、ウィンドウを閉じようとしたときに位置とサイズを記憶して次回実行時に再現するだけのスクリプトです。
var config=(pref("windowPosTest.json")||{loadJSON:function()({})}).loadJSON();
var w=require('Window').Window.create({
 title:"test",
 width:100,height:100,
 x:config.windowX||0, y:config.windowY||0,
 events:{
  close:function(){
   config.windowX=this.x;
   config.windowY=this.y;
   pref("windowPosTest.json",true).saveJSON(config);
  },
 },
}).show();;

最初のpref()では、既に存在している設定ファイルを表すFileオブジェクトを取得しています。
この時、ファイルが存在していなくてnullが返された場合でもエラーにならずにデフォルトのオブジェクトがセットされるようにしておく必要があります。

Windowオブジェクトのcloseイベントでは、設定オブジェクトにウィンドウの現在位置をセットして保存を行っています。
保存時のpref()の呼び出しでは、第2引数をtrueにする必要があります。

スクリプトが不要になったらプログラムのディレクトリを削除するだけで完全に削除できるように、通常はスクリプトのあるディレクトリ内の「users」ディレクトリにユーザー名のディレクトリを作って保存されます。
しかし、スクリプトのあるディレクトリが書き込み禁止になっていたりする場合は、Application Dataディレクトリ内のスクリプトファイル名を名前にしたディレクトリが保存先として選択されます。
また、スクリプトと同じディレクトリに「config」というディレクトリがあれば、そちらが保存先になります。これはプログラムをリムーバブルメディアに入れて持ち出して複数の環境で利用したい場合などのための機能です。

なお、プログラム用のドライブに頻繁な書き込みをしたくないとか、まとめてバックアップ出来た方がいいなどの理由で、設定がApplication Dataディレクトリに保存されることを好むユーザーは、あらかじめApplication Dataディレクトリ内に該当するディレクトリを作成しておくことで、そちらに保存させることも出来ます。

これらの仕様により、pref()関数を使用すれば、簡単にユーザーの好みや実行環境に合った最適な場所にデータを保存できるようになっています。

2010/10/25

自前ウィンドウの任意の部分をドラッグして移動できるようにする

NILScriptの自前ウィンドウ生成機能では、タイトルバーや枠のないウィンドウも生成できます。
しかし、タイトルバーや枠のドラッグによる移動・リサイズが行なえないので、別途移動やリサイズのための動作を用意する必要があります。

ウィンドウの任意の部分をドラッグして移動できるようにするには、hitTestイベントを利用します。
このイベントは、カーソル下にあるのがウィンドウのどの部位かという問い合わせを受取ったときに実行されます。
ここでカーソル下がタイトルバーであると応答するようにすれば、マウスの左ボタンでドラッグしたときに、タイトルバーをドラッグしたときと同じようにウィンドウが移動されるようになります。

これを利用して前回のCPU使用率グラフ表示のウィンドウをドラッグ可能にしたのが、以下のスクリプトになります。
var img=require('Image').Image.create(100,100,"black");
var w=require('Window').PlainWindow.create({
    width:100,height:100,
    graphics:img,
    transparentColor:0x000000,toolWindow:true,
    topmost:true,
    events:{
        hitTest:function(o){
            o.result="caption";
            return(true);
        },
        mbuttonUp:function(o){
            this.close();
        },
    },
});
Thread.create(function(){
    var mon=new (require('SystemMonitor').CPU)(), rate=0;
    while(true){
        w.request(function(){
            img.fillEllipse(1,1,98,98,0x9999FF);
            img.fillPie(1,1,98,98,-Math.PI/2,(Math.PI*2)*rate,0x99FF99,true);
            img.strokeEllipse(1,1,98,98,0x010101,true);
            img.drawTo(w);
        });
        sleep(1000);
        rate=mon.get().usage;
    }
});
w.show();

自前ウィンドウ上に表示した画像を更新・再描画する

Imageオブジェクトの画像をウィンドウに表示するには、Window.create()のオプションオブジェクトのgraphicsメンバにセットするというのは以前説明しましたが、今回は表示する画像の内容を更新する方法について説明します。

ウィンドウの画像を更新するには、Imageオブジェクトの各種メソッドで画像を更新した後、描画対象のWindowオブジェクトを引数にしてdrawTo()メソッドを呼び出します。

この時、複数スレッドからImageオブジェクトに同時アクセスしないように注意する必要があります。
ウィンドウを所有するスレッド上では、Imageオブジェクトを使用した再描画が不特定のタイミングで発生するので、他のスレッド上でImageオブジェクトを操作すると、更新途中の画像が表示されてしまったり、更新処理に失敗してしまったりします。
これを避けるためには、画像の更新処理もウィンドウを所有するスレッド上で実行しなければなりません。

Windowオブジェクトのイベントハンドラ内で更新・再描画を行う場合は特に気にする必要はありませんが、他のスレッドから更新を行う場合は、Windowオブジェクトのrequest()メソッドを使用してください。
request()メソッドは、指定された関数をウィンドウを所有するスレッド上で実行します。この関数内でImageオブジェクトを操作すれば、安全に画像を更新できます。

下記のスクリプトは、SystemMonitorユニットのCPUクラスを利用して取得したCPU使用率を円グラフでウィンドウ上に表示するという例です。
var img=require('Image').Image.create(100,100,"black");
var w=require('Window').PlainWindow.create({
    width:100, height:100,
    transparentColor:0x000000,topmost:true,
    graphics:img,
});

Thread.create(function(){
    var mon=new (require('SystemMonitor').CPU)(), rate=0;
    while(true){
        w.request(function(){
            img.fillEllipse(1,1,98,98,0x9999FF);
            img.fillPie(1,1,98,98,-Math.PI/2,(Math.PI*2)*rate,0x99FF99,true);
            img.strokeEllipse(1,1,98,98,0x010101,true);
            img.drawTo(w);
        });
        sleep(1000);
        rate=mon.get().usage;
    }
});
w.show();

2010/10/23

テキストなどに影や縁取りを付けた画像を生成

Imageオブジェクトに用意されている様々な機能を組み合わせれば、以下の例のようにテキストや図形に影をつけたり縁取りをするなどのエフェクトを加えて描画することも可能です。

この例の画像を生成するスクリプトは、以下のようになります。
var {Image}=require('Image');
var base=Image.create(200,64);
base.drawText("NILScript",0,0,{name:"Impact",size:48,brush:{
    color:[0xFF0000,0x990000],
    gradient:{angle:Math.PI/2, width:60},
    gammaCorrect:true,
    focus:0.9,
    curve:true,
}});

var frame=base.clone();
frame.and(0xFF000000);
frame.matrixFilter([[1,1,1],[1,1,1],[1,1,1]]);
frame.matrixFilter([[1,1,1],[1,1,1],[1,1,1]]);
frame.replaceColor("black","yellow");

var shadow=frame.clone();
shadow.and(0xFF000000);
shadow.blur(3);

var img=Image.create(200,64,"white");
img.drawImage(shadow,{x:4,y:4});
img.drawImage(frame);
img.drawImage(base);

img.save("logo.jpg",{quality:90});
まず、最初に背景無しでImageオブジェクトを生成し、グラデーションブラシを用いてテキストを描画しています。
ブラシの定義オプションには様々なものがあるので、NILScriptのアーカイブ内のdoc/Image.txtの説明を参照してください。

次に、clone()メソッドでテキスト画像のコピーを生成し、and()メソッドでアルファチャンネル以外の値を0にします。
続いて、matrixFilter()メソッドを利用して不透明の領域を太らせます。
matrixFilter()は、各ピクセルと隣接ピクセルのRGBA各値に引数で指定した2次元配列の対応する値を掛けた値の和をそのピクセルの値にするという処理を行います。この時、0未満の値は0に、256以上の値は255になります。
「[[1,1,1],[1,1,1],[1,1,1]]」という配列では、隣接する9ピクセルに一つでも不透明度255のピクセルがあれば不透明度が255になります。

更に、replaceColor()メソッドで色の置換を行います。
この時点では、不透明度255の黒と不透明度0の黒の2つの部分がありますが、第1引数を"black"にした場合は、不透明度255の黒のみが置換されます。
単色ではなくグラデーションなどを使いたい場合は、そのスタイルで塗りつぶした画像にupdateAlpha()メソッドでアルファチャンネルをコピーすると良いでしょう。

次に、出来上がった縁取り部分のコピーを生成し、同様にして全体を黒にした後、blur()メソッドでぼかして、影っぽい画像を生成します。

最後に、白背景の画像を生成し、影を少しずらして貼り付けた後、縁取りと元のテキストを貼り付けて完成です。

2010/10/21

画像にテキストを描画する

NILScriptのImageユニットでは、下記のようにdrawText()メソッドを使用することで、画像上に文字列を書き込むことも出来ます。
var {Image}=require('Image');
var img=Image.create(256,256,"white");

m=img.measureText("NILScript");
img.strokeRect(15,15,m.width+2,m.height+2,"black");
img.drawText("NILScript",16,16);
img.drawText("NILScript",0,64,{name:"Impact",size:48,color:"red",bold:true});
img.drawText('NILScript',0,160,{file :'D:\\myfonts\\AoyagiKouzanFont2.ttf' , size:48});

img.save("drawText.jpg",{quality:90});
単に文字列を表示したいだけなら、文字列と描画開始位置のx,y座標を指定するだけです。この場合、デフォルトのフォント、サイズ、色で描画されます。

第4引数には、様々なオプションを格納したオブジェクトを指定できます。
「name」でフォント名、「size」でピクセル数、colorで塗りつぶし色が指定できます。
また、「file」でフォントファイル名を指定すれば、システムにインストールされていないフォントも使用できます。

実際に描画される文字列の大きさを知りたい場合には、measureText()メソッドを使用します。このメソッドは、第1引数に文字列、第2引数にオプションを指定します。drawText()と違い、x,y座標は指定しません。
このメソッドの返り値は、widthやheightなどのプロパティを持つオブジェクトになります。これは単なるオブジェクトなので、解放処理は不要です。
この情報を利用すれば、テキストの周りにstrokeRect()などで枠を描画したりすることも可能です。

上記の例を実行すると、以下のような画像が出来上がります。

一定時間キー・マウス操作がなかったときに処理を実行

NILScriptのSystemMonitorユニットには、最後にキーボードやマウスの操作が確認されてから経過した時間を取得する「InputIdle」クラスが用意されています。
これを使用すれば、ユーザーが退席している間に何らかの処理を行ったり、作業状況のログを記録したりするツールを作成できます。

InputIdleクラスの基本的な使い方は、以下のようになります。
Main.createNotifyIcon();
var mon=new (require('SystemMonitor').InputIdle)();
Thread.create(function(){
 var idle;
 while(true){
  if(mon.get(true)<5000){ //規定時間
   if(idle){
    idle=false;
    //↓一定時間以上振りに操作を行ったときに実行される処理
    println("wake");
   }
  }else if(!idle){
   idle=true;
   //↓一定時間操作がなかったときに実行される処理
   println("idle");
  }
  sleep(100);//監視間隔
 }
});

無操作時間を取得するには、InputIdleオブジェクトのget()メソッドを呼び出します。
引数がtrueの場合は、プログラムによって生成された操作を除き、実際にユーザーが行った操作のみを考慮した無操作時間が取得されます。
idle変数は、既に無操作時間が規定値を超えているかどうかのフラグです。
最初に無操作時間が規定値を超えた時にtrueにし、無操作時間が規定値以下の時にtrueになっていたらfalseにすることで、一定時間以上の放置と放置からの復帰を検出しています。
上記の例でprintln()関数を呼び出している部分に、放置や復帰を検出したときの処理を記述します。

円グラフを生成する

NILScriptのImageユニットには、扇形の描画を行うstrokePie()やfillPie()などのメソッドも用意されています。
これらを使用すれば、円グラフの生成や表示が行なえます。

円グラフを生成するには、下記のようなスクリプトを記述します。
今回は、グラフ化する項目群を格納した配列をitems変数としてスクリプト中に記述していますが、実際にはファイルから読み込んだり何らかの方法で収集して用意してください。
var {Image}=require('Image');
var img=Image.create(256,256,"white");
var items=[
    {count:100,color:0xFF9999},
    {count:80,color:0x99FF99},
    {count:70,color:0x9999FF},
    {count:40,color:0x66FFFF},
    {count:20,color:0xFF66FF},
    {count:10,color:0xFFFF66},
];
var sum=items.sum('count');
var angle=-Math.PI/2;
for(var i=0,l=items.length;i<l;i++){
    var sweep=(Math.PI*2)*(items[i].count/sum);
    img.fillPie(8,8,240,240, angle, sweep,items[i].color);
    img.strokePie(8,8,240,240, angle, sweep,"black");
    angle+=sweep;
}
img.save("pie_graph.jpg",{quality:90});
まず、配列の各要素のcountプロパティの合計値をsum変数に格納しておきます。
次に、グラフの描画開始角度をangle変数に代入します。0が→の向きを表し、一周がMath.PI*2なので、↑の方向から始めるには「-Math.PI/2」を初期値とします。

配列の各項目ごとのループでは、まず各項目の値を全項目の合計値で割った値に一周を表すMath.PI*2を乗算して、各項目の扇形の角度を求めています。
そして、fillPie()でその範囲を塗りつぶし、strokePie()で輪郭を描画します。
両メソッドの最初の4つの引数は、扇形を含む円が内接する長方形の位置と大きさを表します。
第5引数では開始角度、第6引数が扇形の角度を指定します。
第7引数では、今回は単に色を指定していますが、他にも線の太さや塗りつぶし方法などが指定できます。
描画が終ったら、次の項目の開始角度が今回の項目の範囲の直後になるように、今回の項目が占めた角度を加算します。
これで、以下のようなグラフが作成できます。
描画したグラフは、save()メソッドでファイルに保存したり、Window.create()のgraphicsオプションにセットしてウィンドウに表示したりして利用してください。

2010/10/20

C言語で作成した関数を埋め込み

スクリプト言語はC言語などと比べると10倍近くも処理速度が遅いのが難点です。
10倍しても1ミリ秒足らずで終るような処理でしたらあまり問題はありませんが、C言語なら500ミリ秒で済む処理が5秒もかかるようになってしまうと、実用に耐えません。
画像の全ピクセルに対する処理など、反復処理が何万回にも及ぶ可能性のある処理は、本体部分をC言語で作成しておいて、それを何らかの方法でスクリプトから呼び出すようにするとよいでしょう。
ここでは、NILScriptのImageユニットで各種フィルタ機能の実装に利用している、Cコンパイラで生成したネイティブコードをスクリプト中に埋め込む方法を紹介します。

まず、Microsoft Visual C++(無償のExpress Editionでも可)でプロジェクトを作成し、埋め込みたい関数を記述してコンパイルします。
すると、プロジェクトのディレクトリ内の「Release」ディレクトリにソースコードの拡張子を「.cod」にしたファイルが作られます。
このファイルには、ソースコードから生成されたネイティブコードの16進ダンプや、アセンブラのコードが記述されています。

NILScriptのディレクトリをカレントディレクトリとして「ng tool/codConv ".codファイルのパス"」というコマンドを実行すると、指定された.codファイル中の関数のネイティブコード部分が抽出され、以下のような形式でクリップボードに格納されます。
var negate=(new CFunction(Hex.decode(
 "55 8b ec 53 56 57 8b 75 0c c1 e6 02 8b 5d 14 8b d3 2b de 8b 45 08 8b 7d 10 0f af fa 03 f8 8b d0 03 d6 81 30 ff ff ff 00 83 c0 04 3b c2 7c f3 03 c3 3b c7 7c e9 5f 5e 5b 5d c3"
))).toFunction([],UInt);
これをスクリプト中に貼り付け、「[]」の中に引数の型の定義を記述し、必要なら返り値の型を修正すれば、ネイティブコードの埋め込み定義が出来上がります。
あとは普通の関数と同じように呼び出して利用できます。

なお、Imageユニットのネイティブコード関数の多くは、動作速度を高速化するため、処理のほとんどをインラインアセンブラで記述しています。
何度も使用する値でメモリの読み書きが発生しないようにレジスタに格納したままにするなどの工夫をすることで、C言語からコンパイルしたものより2倍くらい高速化できることもあります。
いきなりアセンブラでプログラムを作るのは大変なので、最初はコンパイラが生成した.codファイルを参考にするとよいでしょう。

2010/10/19

画像を透明度エフェクト付きで合成

Imageユニットによる画像の合成処理では、合成する画像に加工を施すことで、下図のようなエフェクト付きの合成も実現できます。
この例の画像は、下記のようなスクリプトで作成できます。(画像のパスなどの部分は適宜変更してください)
var Image=require('Image').Image;
var img=Image.load('input.jpg');
var logo=Image.load("logo.png");

var mask=Image.create(32,32);
mask.fillEllipse(4,4,24,24,"white");
mask.blur(5);
logo.updateAlpha(mask);

img.drawImage(logo,img.width-logo.width-16,16);

img.save('output.jpg',{quality:90});
free(mask,logo,img);
元画像と合成するロゴ画像を読み込んだら、ロゴ画像と同じ大きさのマスク用Imageオブジェクトを生成し、fillEllipse()で円を描画します。
次に、blur()メソッドで円をぼかします。マスク画像の白い円の部分以外は透明ですので、円周の付近では不透明と透明が混じって段階的に半透明になります。
これを引数として、ロゴ画像のupdateAlpha()メソッドを呼び出すと、不透明度情報がコピーされ、周辺が徐々に透明になるロゴ画像が出来上がります。
これを合成先の画像のdrawImage()メソッドで合成すれば出来上がりです。

半透明で画像を合成

NILScriptのImageユニットが提供する画像処理機能では、不透明度情報(アルファチャンネル)の取り扱いが可能です。
この機能を利用すれば、写真などの上に半透明のロゴを合成するウォーターマークのような処理も簡単に実現できます。

新たに図形やテキストを描画する場合は、半透明の描画色で描画するという手もありますが、既存の画像を半透明にして貼り付けたい場合は、下記のようにして「updateAlpha()」メソッドでピクセルの透明度を一括変更してからdrawImage()で貼り付けるといいでしょう。(ファイル名などは適宜変更してください)
var Image=require('Image').Image;
var img=Image.load('input.jpg');

var img2=Image.load("logo.png");
img2.updateAlpha(127);
img.drawImage(img2,img.width-img2.width-16,16);

img.save('output.jpg',{quality:90});
updateAlpha()は、0~255の値で画像の不透明度を指定します。
これにより、下記の例のように元の画像(波模様)の上に半透明で別の画像を合成できます。

2010/10/18

Imageオブジェクトの画像をウィンドウに表示

NILScriptのImageユニットでは、画像のリサイズや切り抜き、図形やテキストなどの追加、ぼかしや明るさ調節などのフィルタ処理を組み合わせて、様々な画像処理を実現できます。
しかし、処理が複雑になるほど、思い通りの処理を一発で実現することは難しくなります。
処理結果を確認しながら処理内容を修正したい場合などは、処理結果を自前ウィンドウ状に表示するといいでしょう。
単に処理結果の画像を表示するだけなら、下記のようにWindow.create()のオプションで「graphics」にImageオブジェクトを指定するだけです。
ウィンドウのクライアント領域全体にImageオブジェクトが保持する画像を表示するウィンドウが作成されます。
この場合、ウィンドウが閉じられるまでImageオブジェクトを解放しないように注意してください。
var Image=require('Image').Image;
var img=Image.create(256,256,"white");
img.fillPolygon([
    [240,16],
    [192,224],
    [80,96],
    [16,240],
    [64,32],
    [176,160],
],"black",true,true);

var w=require('Window').Window.create({
    width:400,height:400,
    graphics:img,
}).show();

2010/10/04

指定サイズ未満で最大の画質のJPEG画像を生成

Imageオブジェクトには、画像をファイルに保存するsave()の他にも、画像ファイルデータをメモリ上に出力し、そのバッファを指すポインタオブジェクトを返すtoBytes()メソッドも用意されています。

この機能を利用すれば、ファイルに保存することなく圧縮後のファイルサイズを知ることが出来ます。
下記のスクリプトは、圧縮後のサイズが上限値以下になる最大の画質でJPEG画像を保存するという応用例です。
var file=cwd().file('capture.jpeg');
sleep(2000);
try{
    var img=require('Window').Window.active.capture();
    for(var q=100;q>0;q--){
        try{
            var buf=img.toBytes('.jpeg',{quality:q});
            if((buf.size<100000)||(q==10)){
                file.update(buf);
                println(q);
                break;
            }
        }finally{
            free(buf);
        }
    }
}finally{
    free(img);
}
最初に、img変数にアクティブウィンドウのスクリーンショット画像のImageオブジェクトを代入してます。
次に、forループで最大画質の100から1ずつ減らしながら圧縮を試行しています。
toBytes()では、第1引数で拡張子やMIMEタイプで形式を指定します。第2引数の保存オプションで、q変数に格納されている画質を指定しています。
toBytes()が返すPointerオブジェクトでは、sizeプロパティでバイト数が取得できるので、その値が最大値以下ならばファイルへの保存を実行します。
Fileオブジェクトのupdate()メソッドはファイルを上書き保存するメソッドです。引数には多くの場合文字列を指定しますが、Pointerオブジェクトを指定すればバイト列を保存できます。
toBytes()が返したPointerオブジェクトは、不要になったらfree()関数で解放処理を行う必要があります。
最後に、Imageオブジェクトも解放します。

Webサイトにアップロードする画像を生成するときなどは、この例を応用して最適な画質のJPEG画像を生成させてみるといいでしょう。

画面のスクリーンショットを保存

画像処理を行うImageユニットには、ウィンドウのスクリーンショット画像を取得する機能も用意されています。
例えば、下記のようにすることで、アクティブウィンドウのスクリーンショット画像を保存できます。
try{
    var img=require('Window').Window.active.capture();
    img.save('capture.png');
}finally{
    free(img);
}
スクリーンショット画像のImageオブジェクトを取得するには、Windowオブジェクトのcapture()メソッドを呼び出します。Imageオブジェクトが定義されているのはImageユニットですが、WindowオブジェクトはWindowユニットで定義されているので、「require('Window').Window~」のようにすることに注意してください。
なお、capture()メソッドの第1引数にtrueを指定すると、タイトルバーなどのノンクライアント領域を除いた部分の画像を取得することも可能です。

取得したImageオブジェクトはimg変数に代入し、そのsave()メソッドでファイルに保存しています。
最後にfree(img)で取得したImageオブジェクトの解放処理を呼び出す必要がある事に注意してください。

今回は単に保存しているだけですが、縮小したサムネイル画像や、Webページに埋め込むためのHTMLを生成したりしてもよいでしょう。
また、Hotstrokesでホットキーからキャプチャを実行したり、ウィンドウの状態変化を監視するWindow.observe()などを利用して特定のタイミングで自動キャプチャを行わせるなど、様々な応用ツールに利用できるはずです。

画像処理を行うImageオブジェクト

画像処理に関する機能を提供するImageユニットが追加されました。
画像ファイルの読み書きや、リサイズなどの基本的な加工処理、多角形などの描画機能などが用意されています。

例えば、以下のようにすれば、NILScriptのアイコンなどに使われているロゴ画像を生成できます。
var file=cwd().file('nilscript.png');
var img=require('Image').Image.create(256,256,"white");
img.fillPolygon([
    [240,16],
    [192,224],
    [80,96],
    [16,240],
    [64,32],
    [176,160],
],"black",true,true);
img.save(file);
free(img);
Imageクラスのcreate()メソッドで、幅、高さ、塗りつぶしなどを指定して新規のImageオブジェクトを生成できます。
ImageオブジェクトのfillPolygon()メソッドで、一連の点を結ぶ直線で囲まれた領域を塗りつぶします。
そして、saveメソッドで作成した画像を指定したファイルに保存しています。
Imageオブジェクトはfree()関数で明示的に解放する必要があります。特に常駐スクリプトや大量の画像を処理するスクリプトでは、忘れないように注意してください。

描画系のメソッドはまだ一部しか実装されていませんが、近いうちに線や文字の描画なども実装する予定です。

2010/09/27

ウィンドウを画面の横半分などにリサイズ・移動

NILScriptのWindowユニットで提供されるクラス群では、スクリプトで生成した自前ウィンドウも他のプロセスが所有するウィンドウも、共通のメソッド・プロパティで操作可能です。
移動やリサイズなどの一般的な操作から、表示スタイルの変更などの手動ではできない操作まで、様々な操作を行なえます。
ウィンドウを移動したりリサイズするには、「moveTo()」メソッドを使用します。移動せずリサイズするだけの「resize()」メソッドもあります。

ウィンドウをデスクトップの横半分を占めるように広げたい場合などは、ディスプレイの情報を取得する「Display」クラスを使い、下記のようにするとよいでしょう。
var {Window,Display}=require('Window');
var a=Display.active.work;
Window.active.moveTo(a.left,a.top,a.width/2,a.height);
Displayクラスには、プライマリディスプレイを取得する「Display.primary」の他、アクティブウィンドウがあるディスプレイを取得する「Display.active」や、マウスカーソルがあるディスプレイを取得する「Display.pointed」などもあります。
Displayオブジェクトの「work」プロパティでは、タスクバーなどの領域を除いた作業領域の範囲を取得できます。
これらの値を元にした座標やサイズを「Window.active.moveTo()」に渡せば、画面に合せてアクティブウィンドウを移動・リサイズできます。
「Window.find()」などを使えば、アクティブウィンドウ以外の特定のウィンドウを表すWindowオブジェクトを取得して操作することも可能です。

Hotstrokesの操作に対して割り当てて好きなときに呼び出したり、「Window.observe()」でウィンドウの出現などを監視して自動操作したり、ブラウザなどの常用ソフトのウィンドウをリサイズしてすぐに終了するスクリプトを作成して必要なときに実行したりして利用するといいでしょう。


なお、ブラウザなど常に起動しているソフトのウィンドウの位置・サイズを固定して長時間常時表示させていると、枠などの変化のない部分の映像がディスプレイに焼き付いてしまうことがあるので注意してください。
液晶ディスプレイでは焼き付きは起こらないという話がありますが、1万時間くらい同じ映像を表示させているとさすがに多少焼き付いてしまうようです。
画面全体が#202020くらいの暗い色になっているときでないと分からないので、全画面で動画再生やゲームをする人でなければ気にならないでしょうが。

2010/09/26

ウィンドウ生成機能でデスクトップ上に字幕を表示

NILScriptでは、ウィンドウを生成して情報を表示したり入力を受け付けたりすることも出来ます。
通常はメモ帳などのアプリケーションと同じタイトルバー付きのウィンドウになりますが、Window.create()のオプションでスタイルを指定する事で、枠のないウィンドウやタスクバーに表示されないウィンドウなども作れます。
今回は、これらのオプションを駆使してデスクトップ上に字幕を表示させる例を紹介します。

下記のようなスクリプトで、上の画像の「Warning!!」の部分のようなテキストを表示させられます。
var win=require('Window').Window.create({
    isPopupWindow:true,hasTickFrame:false,hasBorder:false,hasDlgFrame:false,
    toolWindow:true, 
    topmost:true,
    transparent:true,
    transparentColor:0xFF01FF,
    alpha:196,
    width:0,height:0,
    children:{ie:{type:require('ATL').Trident,top:0,left:0,right:0,bottom:0}},
});
win.resize(800,120,true).moveToCenter();
win.ie.update('<body style="margin:0;background-color:#FF01FF;overflow:hidden;text-align:center; font-size:96px;color:#FF0000;">Warning!!</body>');
win.show();
sleep(3000);
win.close();
オプションの1行目の4つの項目は、タイトルバーやウィンドウ周辺の枠を無くすオプションです。余計な物をなるべく表示させたくないときに利用するといいでしょう。
「toolWindow」は、タスクバー上にボタンを表示しないようにするするオプションです。
これらのオプションを指定すると、通常の方法ではウィンドウを閉じられなくなるので、必要に応じてウィンドウを隠したり閉じたりする処理を用意してください。

「topmost」は、ウィンドウを常に最前面に表示させるオプションです。アクティブウィンドウの上に表示させたい場合などに指定します。

「transparent」はクリックなどを下のウィンドウに透過させるオプションです。主にtopmostと組み合わせて使います。クリックして操作する必要のない情報表示用ウィンドウで、誤ってクリックして操作の妨げとなることを防ぎたい場合などに指定するといいでしょう。

「transparentColor」はウィンドウ上の特定の色の部分を完全に透明にするオプションです。字幕などを表示するときは、このオプションで背景を透過させます。透過色は「0xRRGGBB」の形式の数値で指定します。本来透過させるつもりでない部分に使われている色と被ってしまわないように、なるべく使われなさそうな色を指定します。
また、「alpha」では、透過色以外のウィンドウ全体の不透明度を0~255の数値で指定できます。ゼロに近いほど透明になります。

ウィンドウの大きさを指定する「width」と「height」は、作成オプションでは両方ゼロにしておき、後から「resize()」で適当な大きさにしています。これは、たまに透過指定した背景色が描画されていない状態で一瞬だけ表示されてしまうことがあるのを防ぐためです。なお、「moveToCenter()」は、ウィンドウを画面中央に位置するように移動するメソッドです。

ウィンドウ内に配置するコントロールを定義する「children」では、背景色や文字スタイルなどを柔軟に指定できるように、ウィンドウ全体にIEコンポーネントを配置することにします。
IEコンポーネント上に表示する内容は、後の「win.ie.update()」で指定します。ここでは、body要素のstyle属性で文字のスタイルや背景色を指定したり、マージンやスクロールバーなどを削除しています。背景色には、transparentColorで指定したのと同じ色を指定します。
body要素内のHTMLには、今回は単なるテキストを指定しましたが、HTMLで装飾を加えたりしてもよいでしょう。ただし、スタイルシートの透過機能などで背景色と混じった部分は透過されなくなってしまうので注意してください。

HTTPでファイルのアップロードを行う

NILScriptには、HTTPでリクエストを送信してレスポンスを得る機能も用意されています。
一発でレスポンスボディを得られる手軽なメソッドや、レスポンスボディをストリームとして逐次読み込みするオブジェクトを返すメソッドなどが用意されている他、リクエストヘッダの指定やプロキシの使用など、柔軟な動作設定も可能です。
WSHなどでは実現が面倒なファイルのアップロードも、下記の例のように簡単に実現できます。
require('HTTP').HTTP.post(url,{
 upfile:new File(filePath),
 pass:"test",
 com:"this is test of file upload",
});
ファイルのアップロードを行うには、POSTによるリクエストを行うpost()メソッドを使用します。
第1引数はリクエストするURL(HTMLのform要素のaction属性で指定される物)を、第2引数にはフォームの項目名と項目内容のペアを列挙したオブジェクトを指定します。
ここで項目内容としてFileオブジェクトが指定されていると、そのファイルの内容がアップロードされます。
上記の例では、変数に格納されたフルパスを元にFileコンストラクタを呼び出していますが、実行時にファイルの指定を受け付ける様々な方法の記事で紹介したような方法で得たFileオブジェクトを指定してもよいでしょう。
post()メソッドは、アップロード完了後に表示されるページの内容などのレスポンスボディ文字列を返します。アップロードされたファイルのURLを得たい場合などは、返り値の内容を正規表現マッチングで調べたりするとよいでしょう。

なお、NILScriptのHTTPには、非同期的な処理を行う機能は用意されていないません。
リクエストを開始したら即座に次の処理に進みたい場合には、「Thread.create()」で生成したスレッド内でリクエストを実行することで非同期化してください。

2010/09/24

実行時にファイルの指定を受け付ける様々な方法

ファイルに対して何らかの処理を行うスクリプトでは、実行時に処理対象ファイルを指定できるようにしておくと便利です。
今回は、ファイルの指定を受け付ける方法をいくつかまとめて紹介します。

ダイアログ

「Dialog」ユニットに用意されているFileDialogを使用すれば、標準的なファイル選択ダイアログを表示してファイルを選択させられます。
素早い操作には向きませんが、ソフトの操作に慣れていないユーザーに使わせる場合などには、この方法が一番分かりやすいでしょう。

下記は、ダイアログを表示して選択させたファイルの合計サイズを「alert()」関数でダイアログ表示するという例です。
FileDialogでは、複数選択の可否や初期表示ディレクトリなど様々な挙動をオプションで指定できます。詳細な説明は、同梱の「doc\Dialog.txt」を参照してください。
show()の返り値は、multiSelectがfalseの場合は一つのFileオブジェクト、trueの場合はFileオブジェクトの配列になります。Fileオブジェクトの機能については、doc\base_io.txtを参照してください。
「sum()」は、配列オブジェクトに追加された拡張メソッドで、指定プロパティの合計値を返します。
var fileArray=require('Dialog').FileDialog.show({
 directory:"D:\\bin",
 multiSelect:true,
});
alert(fileArray.sum("size"));

コマンドライン引数

コマンドライン引数で対象ファイルを指定できるようにしておけば、スクリプトファイルやショートカットのアイコンへのドラッグ&ドロップの他、「送る」メニューへの登録やコマンドプロンプトからの実行、他のソフトの外部アプリ呼び出し機能での利用など、様々な使い方が可能になります。

スクリプトへのコマンドライン引数は、「Main.params」というプロパティで配列として取得できますが、配列の各要素は単なる文字列ですので、ファイルに関する操作を行うには、Fileオブジェクトに変換してやる必要があります。 
コマンドプロンプトなどから相対パスを指定して実行した時にも対応できるように「cwd().file()」を利用するといいでしょう。「cwd()」はカレントディレクトリのDirectoryオブジェクトを得る関数、「file()」は相対/絶対パスで指定したファイルのFileオブジェクトを得るメソッドです。

コマンドライン引数で与えられたファイルの合計サイズを表示するスクリプトは、以下のようになります。
var fileArray=[];
for(var i=0,a=Main.params,l=a.length;i<l;i++){
 fileArray.push(cwd().file(a[i]));
}
alert(fileArray.sum("size"));
さらに使い勝手を高めるには、コマンドライン引数が指定されなかった場合にファイル選択ダイアログを表示するなどの処理を追加するといいでしょう。


クリップボード経由

NILScriptには、エクスプローラなどでのCtrl+Cでコピーされたファイルリストを読み取る機能が用意されています。
この機能を利用すれば、クリップボード経由で処理対象ファイルを指定する方式のスクリプトを作成可能です。
ファイル選択ダイアログやウィンドウへのドラッグ&ドロップよりも素早い操作で利用できるので、「送る」などに登録するほど利用頻度が高くないスクリプトを作る場合におすすめです。

クリップボードに格納されているファイルの配列は、下記のようにすることで取得できます。ファイルが格納されていない時はfilesがundefinedになるので、ファイル以外をコピーしている場合はゼロ個のファイルとして扱いたい場合は、「||[]」を付けて空の配列に置き換わるようにしておきます。
なお、クリップボードの内容は確認しづらいので、重大な処理を行うスクリプトでは、間違ったファイルをコピーした状態で実行してしまわないように、確認ダイアログを表示する「confirm()」などによる確認処理を組み込んでおくとよいでしょう。
var fileArray=require('Clipboard').Clipboard.files||[];
if(confirm(fileArray.join("\n"))){
 alert(fileArray.sum("size"));
}


ウィンドウへのD&D受け付け

ウィンドウを表示するタイプのツールでは、ウィンドウにファイルをドラッグ&ドロップすることでファイルを指定できるようにしておくと便利です。
本来はウィンドウを必要としない処理でも、処理対象ファイルを好きな時に指定できるようにしたい場合などは、ドラッグ&ドロップによるファイル指定を受け付けるためのウィンドウを用意するとよいでしょう。
ウィンドウへのファイルドロップを受け付けるには、Window.create()のオプションで「acceptFiles」をtrueにし、「events」の「dropFiles」にイベントハンドラ関数を指定します。
イベントハンドラの第1引数オブジェクトの「files」メンバで、ドロップされたファイルを表すFileオブジェクトの配列を取得できます。
with(require('Window')){
 Window.create({
  width:320,height:240,topmost:true,
  children:{
   edit:{
    type:Edit,multiline:true,readOnly:true,
    left:0,top:0,right:0,bottom:0,
   },
  },
  acceptFiles:true,
  events:{
   dropFiles:function(o){
    this.edit.text=o.files.join("\r\n")+"\r\n合計バイト数: "+o.files.sum("size");
   },
  },
 }).moveToCenter().show();
}

省電力状態からの自動復帰も可能なタイマー機能

NILScriptに用意されている「Timer」クラスには、コンピュータがサスペンドやハイバネート状態になっても指定時間経過後に自動復帰させる機能が用意されています。
この機能を利用すれば、サーバが空いている時間帯を利用してのダウンロードなどの待ち時間の長い自動処理を行う際に、予定時刻を待っている間コンピュータを起動したままにしておく必要が無くなり、電力を節約できます。
この機能を利用するには、Timerオブジェクトのコンストラクタで第4引数にtrueを指定します。Timerの使用方法については、同梱のdoc\base_task.txtを参照してください。

復帰とは逆に、スクリプト上からサスペンドやハイバネートを実行するためのメソッドも用意されています。これらは「System.suspend()」や「System.hibernate()」というSystemクラスのメソッドとなっています。
また、サスペンドやハイバネート状態に移行し、指定時間後に自動復帰するという処理を行う「System.suspendFor()」や「System.hibernateFor()」というメソッドも用意されています。これらの機能の説明は、doc\base_main.txtにあります。

なお、タイマーによる復帰機能では、タイマーで指定した時間に復帰処理が始まるため、復帰が完了して予約された処理が実行されるまでには数秒~数十秒のタイムラグがあることに注意しなければなりません。
テレビの自動録画のように遅れることが許されない処理では、予約実行のタイマーとは別に、予定時刻の1~2分前に自動復帰させるためのタイマーをセットしておくとよいでしょう。
この場合、「System.awake()」というメソッドを使用すれば、単にサスペンド・ハイバネートから復帰するだけのタイマーを生成できます。

2010/09/21

プロセスのCPU使用率を監視して自動処理

先日の更新で、CPUの使用状況などを取得するためのクラスを提供する「SystemMonitor」ユニットが追加されました。
このユニットには、「CPU」、「ProcessCPU」、「Network」、「PerformanceCounter」の4つのクラスが用意されています。
「CPU」クラスでは、システム全体のCPU使用率が取得できます。マルチプロセッサの個別の稼働状況も取得可能です。
「ProcessCPU」クラスでは、指定のプロセスのCPU使用率を取得できます。
「Network」は、ネットワークインターフェイスの転送量を取得するクラスです。複数のネットワークインターフェイスの個別の転送量も取得できます。
他にも、「PerformanceCounter」を使うことで、HDDの読み書き量など様々な情報を取得できます。
また、標準クラスである「System」には「memory」メンバとしてシステムのメモリ使用状況を取得するメンバが追加された他、以前からProcessインスタンスの「pagefileUsage」プロパティや「workingSetSize」プロパティでプロセスごとのメモリ使用状況を取得可能になっています。

これらの機能は、タスクマネージャのような情報表示だけでなく、自動処理を進める条件などとしても役立つでしょう。
例えば、動画のエンコードなどの時間のかかる処理が終ってから別の処理を自動実行させたいけれどエンコードソフトがプロセスの存続やウィンドウのテキストなどによる終了判定がしにくい仕様になっている場合に、プロセスのCPU使用率が低くなった時点で処理完了とみなすといった具合です。

このような場合、以下の例のようなスクリプトで、処理の完了まで待機させることが出来ます。
var p=Process.find("VirtualDub.exe");
try{
    var mon=new (require('SystemMonitor').ProcessCPU)(p);
    sleep(1000);
    while(mon.get()>0.05){
        sleep(1000);
    }
}finally{
    free(mon);
}
sleep(10000);
//以降にエンコード終了後の処理を記述

最初の行で、監視対象となるProcessオブジェクトを取得しています。
この例では、既に実行中のプロセスを取得する「Process.find()」を使っていますが、「Process.create()」でプロセスの起動から自動化してもよいでしょう。
「var mon=new (require('SystemMonitor').ProcessCPU)(p);」で、そのプロセスのCPU使用率を取得するためのProcessCPUオブジェクトを生成しています。
プロセスのCPU使用率を取得するには、このオブジェクトの「get()」メソッドを呼び出します。
このメソッドは、前回呼び出し時かインスタンス生成時から今回呼び出し時までの期間のCPU使用率を返すので、インスタンス後に即座に呼び出してしまうと正常な値が取得できないため、最初のget()呼び出しの前にもsleep()が呼び出されるようにしておく必要があります。
get()の返り値は0~1の間の小数となるので、上記の例ではCPU使用率が5%以下になるまでループし続けることになります。
CPU使用率が低くなった直後は、まだファイルへの書き込みなどの処理が行われている可能性があるので、数秒程度sleep()してから次の処理に進むようにするとよいでしょう。
なお、「free(mon)」は、監視オブジェクトで使用したリソースを解放するための処理ですが、一連の処理を実行してすぐに終了するタイプのスクリプトでは、省略しても問題は無いでしょう。

なお、CPUやNetwork、PerformanceCounterクラスも、インスタンスを生成してget()で値を取得するという流れはProcessCPUと同じです。
コンストラクタに渡す引数やget()が返す内容などの詳しい仕様は、同梱のdoc\SystemMonitor.txtを参照してください。

2010/09/01

クリックしたウィンドウのクラス名をコピーするスクリプト

Hotstrokesの条件割り当てなどでは、ウィンドウの「クラス名」を指定する事がよくあります。
ウィンドウのクラス名は、Windowsのウィンドウの種類を表す文字列です。NILScriptに用意されているクラスの仕組みとは、特に関係はありません。
タイトルバーの文字列と違って、クラス名は画面に表示されたりしないので、調べるには何らかのツールを使う必要があります。
そこで、クリックしたウィンドウのクラス名をクリップボードにコピーするスクリプトの例を紹介します。
use('Mouse','Window','Clipboard');
Mouse.wait('lbuttonUp');
Clipboard.text=Window.fromPoint(Mouse.x,Mouse.y).className;
実行すると「Mouse.wait('lbuttonUp');」によってマウスの左ボタンが押されるまで待機状態になるので、クラス名を調べたいウィンドウをクリックしてください。
次の行では、クリップボードにテキストとしてウィンドウのクラス名がセットされます。
「Window.fromPoint()」は、デスクトップ全体における指定座標に存在する最も前面のウィンドウを表すWindowオブジェクトを返すクラスメソッドで、Windowオブジェクトの「className」プロパティでクラス名を取得できます。

最小化されたウィンドウをタスクトレイに格納する

NILScriptでは「Main.createNotifyIcon()」でスクリプトの終了などの操作を行うためのタスクトレイアイコンを生成できますが、他にも任意の数のタスクトレイアイコンを生成できます。
これを利用し、特定の種類のウィンドウが最小化された時にそのウィンドウをタスクバーに表示されないようにして代わりにタスクトレイアイコンとして表示するというスクリプトの例を紹介します。

Main.createNotifyIcon();
var targetClasses=['Notepad','MSPaintApp'];
var {Window}=require('Window');
var prev=Window.active;
var myWindow=Window.create();
Window.observe('activate',function(o){
    if(prev.minimized && (o.window.handle!=prev.handle) && targetClasses.contains(prev.className)){
        var w=prev;
        w.hide();
        var restore=function(){
            w.show();
            w.restore();
        };
        Main.observe('exit',restore);
        myWindow.addNotifyIcon({
            icon:w.icon,
            text:w.title,
            events:{
                lbuttonUp:function(){
                    restore();
                    Main.unobserve('exit',restore);
                    this.free();
                },
            },
        });
    }
    prev=o.window;
});

「Window.observe()」でウィンドウの状態変化を監視するイベントハンドラを登録しています。
Window.observe()が利用しているシェル用のフック機能では、最小化を直接検出することは出来ないようなので、アクティブウィンドウが変った時に前にアクティブだったウィンドウの状態を調べるという方法で代用しています。
最小化ボタンによる通常の最小化ならば、この方法で問題なく検出できるようです。

Window.observe()では、イベントハンドラの第1引数に与えられるオブジェクトの「window」メンバでイベントが発生したウィンドウを参照できますが、ここでは「prev」変数に代入されている直前にアクティブだったウィンドウの方が対象となります。prevのウィンドウが最小化されていなかったり、対象クラス名に一致しない場合は、単にprev変数に今回アクティブ化されたウィンドウを代入して終ります。
なお、prev変数は最初の定義時にアクティブウィンドウを表す「Window.active」で初期化してあります。

対象ウィンドウが最小化された場合、ウィンドウの不可視化、スクリプト終了時にウィンドウを復元する処理の登録、トレイアイコンの登録の3つの処理を行います。
その前に、最初の「var w=prev」で、その時最小化されたウィンドウを表すオブジェクトを変数に代入しておく必要があります。こうしておかないと、prev変数が書き換わってしまい、どのウィンドウを対象にしていたのか分からなくなってしまうからです。
このローカル変数「w」はJavaScriptの「クロージャ」という仕組みでrestoreやlbuttonUpにセットした関数から参照されており、次に最小化時処理が行われる時には、新たなw変数が生成され、新たに生成されたrestoreやlbuttonUpの関数から参照されます。

ウィンドウの可視化と最小化からの復元を行う処理は、関数として「restore」変数に代入して、「Main.observe('exit',restore)」でスクリプト終了時にも実行されるようにイベント割り当てを行っておきます。

トレイアイコンの登録は、自前ウィンドウのオブジェクトの「addNotifyIcon()」メソッドで行います。ここでは、あらかじめWindow.create()で生成して「myWindow」変数に代入しておいた空の自前ウィンドウを使用しています。
引数となるオプションオブジェクトのiconメンバには、今回最小化されたウィンドウのアイコンである「w.icon」を指定しています。ここでは、他にもアイコンファイルや実行ファイルのパスを指定することも可能です。
トレイアイコンの操作イベントに対する動作割り当てを定義するeventsでは、左クリック時に発生するlbuttonUpイベントに対して、復元関数の呼び出しとスクリプト終了時のイベント登録の解除、トレイアイコン自身の削除の3つの処理を行う関数を割り当てています。

このトレイアイコン生成機能を利用すれば、様々なツールを手軽に実現できるでしょう。

選択テキストをWeb検索するHotstrokes定義

選択中の文字列をパラメータにして検索サイトのURLを開くなど、エディタやブラウザでの選択文字列に対して処理を行いたいことはよくあります。

NILScriptでは、「Window」ユニットの「Edit」クラスに用意されている「selection」プロパティで選択文字列の読み書きを行なえますが、エディットコントロール側がエディタ関連のウィンドウメッセージに応答するように作られている必要があり、全ての選択テキストに対して有効なわけではありません。
エディタ関連メッセージに未対応のテキストエディタやブラウザなどからは、別の方法で選択テキストを取得しなければなりません。
一番汎用的だと思われるのは、Ctrl+Cなどを送信してコピーを実行し、クリップボードにコピーされたテキストを取得する方法でしょう。

下記に挙げるのが、エディタ関連メッセージに対応したコントロールではselection、それ以外のコントロールではクリップボードを使用して選択テキストを取得しGoogle検索するという機能をHotstrokesに割り当てるスクリプトです。
Main.createNotifyIcon();
var {Clipboard}=require('Clipboard');
var {Hotstrokes}=require('Hotstrokes');
var {Edit}=require('Window');
Hotstrokes.defineConditions({
 'Editor':function()(['Edit','TEditorX'].contains(this.focusedWindow.className)),
}).map({
 'Win+G':{
  'Editor':function(){
   this.cancel();
   exec('http://www.google.com/search?q='+encodeURIComponent(Edit.focused.selection));
  },
  '':function(){
   this.cancel();
   Thread.create(function(){
    var bk=Clipboard.text;
    Hotstrokes.send("Ctrl+C");
    sleep(100);
    exec('http://www.google.com/search?q='+encodeURIComponent(Clipboard.text));
    if(bk){
     Clipboard.text=bk;
    }
   });
  },
 },
}).register();
最初のdefineConditions()では、「Editor」という条件名にエディタ関連メッセージに対応したコントロールのクラス名を条件とする関数を定義しています。
「contains()」は、引数で指定した値を要素として持つかどうかを返す配列の拡張メソッド、「this.focusedWindow.className」は、ストロークが受理された時にフォーカスを持っているコントロールのクラス名を得る式です。
上記の例では、メモ帳などで使われる基本的なテキスト編集欄であるEditや、VxEditorで使われているTEditorXを対象として定義しています。

map()では、Windowsキー+Gに対して、条件に応じた動作を割り当てています。
「this.cancel()」というのは、Gキーの押し下げストロークの本来の動作を無効化するという動作です。これがないと、「g」という文字が入力されて選択テキストが上書きされてしまうでしょう。

Editorの場合の動作では「Edit.focused.selection」でフォーカスのあるエディタの選択テキストを取得しています。「Edit.forused」は、フォーカスのあるコントロールをEditクラスのインスタンスとして取得するというEditクラスのプロパティです。
exec()は、システムの関連付けに従ってファイルやURLを開く関数、encodeURIComponent()はURLのパラメータとして使えるように文字列をエンコードするJavaScript標準の関数です。


他の条件に一致しなかった時に参照されるデフォルト動作である「''」に対する割り当てでは、this.cancel()以外を「Thread.create()」によって新規スレッドで実行しています。
sleep()などを含み時間がかかる処理は、そのまま実行すると動作が不安定になってしまう場合があるので、このようにして新規スレッドで実行させるように習慣づけておくとよいでしょう。
スレッド内では、クリップボードのテキストを変数にバックアップし、Ctrl+Cを送信し、コピー処理が完了するのをsleep()で待って、検索結果のURLを開き、バックアップしたテキストがあれば書き戻すという処理を行っています。

2010/08/31

ActiveXオブジェクトに対応

ここ数日の更新で、ActiveXのオブジェクトを扱う「COM」ユニットと「ATL」ユニットが新たに追加されました。

COMユニットでは、WSHのActiveXObjectのように、InternetExplorerなどのプログラムの制御などを行なえます。
WSHではIDispatchが実装されたオブジェクトしか扱えませんが、NILScriptのCOMでは「Unknown.define()」でメソッドの定義を指定してラッパークラスを登録することで、他のインターフェイスしか持たないオブジェクトの操作も可能となっています。
イベントハンドラの登録は、EventMixinによって実装されており、他のNILScriptオブジェクトと同じ感覚で使用できます。
一方、Dispatchオブジェクトを手動で開放しなければならないなど、WSHのJScriptとはいくつかの点で使い勝手が異なっているので注意してください。

ATLユニットでは、Windowユニットで提供される自前ウィンドウ生成機能でIEコンポーネントなどのActiveXコントロールを使用するためのクラスが提供されます。
IEコンポーネントをHTTPDと組み合わせれば、様々なインターフェイスを手軽に実現できるでしょう。

2010/08/20

クリップボードにファイルをコピーする

NILScriptのClipboardユニットでは、エクスプローラなどでCtrl+C/Ctrl+Vでのコピー・移動を行う時に使用されるファイルリスト形式なども扱えます。
Hotstrokesなどの機能と組み合わせれば、ファイルの管理を効率化する機能を実現するのに役立つはずです。

クリップボードにファイルをコピーするには、「Clipboard.files」というプロパティにFile/Directoryオブジェクトやファイルパス文字列、あるいはその配列を代入すればよいのですが、filesのみをセットした場合「切り取り」と「コピー」のどちらの扱いになるかが不定であることに注意しなければなりません。この状態でエクスプローラに「貼り付け」を行った場合、エクスプローラから「切り取り」していた時と同じように、ファイルが移動されてしまいます。

移動ではなくコピーにしたい場合、複数の形式を同時にセットできる「contents」プロパティを使って、「isMove」という形式と共にセットします。
isMoveは、その名の通り「移動」扱いにするかどうかを表す情報で、真偽値として読み書きできます。
下記のようにすれば、指定のファイルを「コピー」扱いでクリップボードに格納できます。
var Clipboard=require('Clipboard').Clipboard;
var f=Main.directory.file('readme.txt');
Clipboard.contents={isMove:false, files:f};

クリップボードに格納されたファイルは、Clipboard.filesを参照することで、File/Directoryオブジェクトの配列として取得できます。
この時、ファイルリストがコピーされていない場合はundefinedとして取得されることに注意してください。undefinedを配列として扱おうとすると、エラーになってしまいます。
ファイルリストがコピーされていない場合は要素数ゼロの配列とみなしてしまってよい場合は、下記のように「||[]」を加えておくとよいでしょう。
var Clipboard=require('Clipboard').Clipboard;
for(var i=0, a=Clipboard.files||[], l=a.length; i<l; i++){
 println(a[i].path+'('+a[i].size+')');
}

また、下記のようにして、ファイルリスト形式が格納されている場合のみ処理を行わせるという方法もあります。
var Clipboard=require('Clipboard').Clipboard;
if(Clipboard.hasFormat('files')){
 for(var i=0, a=Clipboard.files, l=a.length; i<l; i++){
  println(a[i].path+'('+a[i].size+')');
 }
}

2010/08/19

Shift-JISやEUC-JPでURLエンコードを行う

HTTPでURLを扱う時は、ASCII文字しか使用してはならないことになっているため、使用できない文字は「%」の後に2桁の16進数文字を続けて1バイトを表す「パーセントエンコーディング」という方式でエンコードしなければなりません。
日本語などのマルチバイト文字の文字コードには特に規定がないため、WebサイトによってUTF-8だったりEUCだったりまちまちです。

URLをブラウザなどで開く場合、正しくエンコーディングされていないと、ページが開かれなかったり検索ワードが文字化けしてしまうことがあります。
JavaScriptでは、「encodeURI()」や「encodeURIComponent()」という関数でパーセントエンコーディングを行なえますが、文字コードはUTF-8に固定されており、Shift-JISやEUCなどの文字コードでのエンコードはできません。

Shift-JISやEUCでエンコードされたURLを生成したい場合は、NILScriptに用意されている「MBString.encodeURI()」や「MBString.encodeURIComponent()」を利用してください。
例えば、「keyword」という変数に格納された文字列をShift-JISにエンコードして埋め込んだURLを関連付けに従って開くには、以下のようにします。
実際には、Hotstrokesなどの機能と組み合わせて利用すると良いでしょう。
exec('http://search.example.com/?q='+MBString.encodeURIComponent(keyword,'sjis'));
MBString.encodeURIComponent()は、通常のencodeURIComponent()と違い、第2引数で文字コードを指定できます。

URLの文字コードを判別するには、ブラウザ上で利用したいサービスのキーワード入力欄から「あ」を検索してみるとよいでしょう。検索結果ページのURLに「%82%A0」が含まれれば「sjis」、「%A4%A2」が含まれれば「euc」、「%E3%81%82」が含まれれば「utf8」を指定すればよいはずです。

2010/08/18

プロセスの終了を監視して再起動したりする

NILScriptのProcessオブジェクトにはイベント機構が用意されており、「exit」イベントでプロセスの終了を検出できます。システムによるプロセス終了通知機能を利用しているので、定期的にプロセスの有無をチェックするよりも少ない負荷で、高速に応答できます。

この機能を利用すれば、サーバなどのプロセスが予期せず終了した時に自動的に再起動するというような処理を、下記のような簡単なスクリプトで実現できます。
var p=Process.find('mspaint.exe')||Process.create('mspaint');
while(true){
 p.wait('exit');
 p=Process.create('mspaint');
}
「Process.find('mspaint.exe')」は、指定のプロセスが存在していればProcessオブジェクトを返すという処理です。プロセスが存在していなければ「undefined」という値が返ります。「Process.create('mspaint')」は、指定のコマンドラインのプロセスを起動を試み、成功すればProcessオブジェクトを返します。
「||」は、左の式を評価した結果がオブジェクトなどのtruthy値ならその値を返し、undefinedなどのfalsy値ならば右の式を評価した結果を返すという演算子です。左の式がtruthyの場合は、右の式は無視されます。
これらを組み合わせると、「プロセスが存在していればそれを表すProcessオブジェクトを、存在していなければ新たにプロセスを起動してそのProcessオブジェクトを返す」という処理になります。
このような「存在していなければ新規に作成」というテクニックは、よく利用するので覚えておくと良いでしょう。

p.wait('exit')が肝心の当該プロセスの終了まで待機する処理です。当該プロセスが順調に動作し続ければ、スレッドはこの部分で止まったままとなります。当該プロセスが終了すると、p.wait()から制御が戻り、次の処理に進みます。
次の行では、新たに起動したプロセスのProcessオブジェクトをp変数に代入します。
この2つの処理はwhile文で繰り返し実行されます。

この処理を他の常駐機能と一緒のスクリプトにまとめたい場合は、下記のようにThread.create()でスレッド化します。
Thread.create(function(){
 var p=Process.find('mspaint.exe')||Process.create('mspaint');
 while(true){
  p.wait('exit');
  p=Process.create('mspaint');
 }
});
プロセス監視機能を単独で使用したい場合でも、このようにスレッド化しておいて、「Main.createNotifyIcon();」という行を追加しておけば、タスクトレイアイコンから終了させられるようになって便利です。

スクリプトの常駐機構

NILScriptでは、キーボードやマウスなどのイベントハンドラ関数を登録している時にスクリプトの本体が末尾まで実行されてもプロセスが終了してしまわないように、「常駐カウント」という仕組みが用意されています。
イベントハンドラ関数の登録などが行われ、常駐カウントが増やされた状態でスクリプトの本体が末尾まで実行されると、メインスレッドは終了せずに常駐状態になります。イベントハンドラ関数の登録が解除されたりして常駐カウントが0になると、常駐状態から抜けてプロセスが終了します。

NILScriptに元々用意されている機能では、必要に応じて自動的に常駐カウントが増減されますが、Thread.create()で自前のスレッドを生成して、必ず最後まで実行して欲しい処理を並列実行させる場合には、下記のようにして明示的に常駐カウントを増減させる必要があります。
function count(name,num,interval){
 Thread.create(function(){
  for(var i=0;i<num;i++){
   println(name+': '+i);
   sleep(interval||100);
  }
  Main.unreside();
 });
 Main.reside();
}
count("A",10);
count("B",6,400);
「Main.reside()」が常駐カウントを増やすメソッド、「Main.unreside()」が常駐カウントを減らすメソッドです。
Main.reside()は、監視スレッド側ではなく、メインスレッド内で呼び出す必要がある事に注意してください。これは、監視スレッド内で呼び出すと、Main.reside()が実行される前にメインスレッドが終了してしまうことがある為です。

ユーザーの操作でスクリプトを終了させられるようにしたい場合は、「Main.createNotifyIcon()」でタスクトレイアイコンを作成するか、Hotstrokesで何らかのキー操作に「exit()」関数の呼び出しを割り当てておくと良いでしょう。exit()は、スクリプトを終了させる関数です。
なお、トレイアイコンやHotstrokesを使用すると常駐カウントが増やされるので、別途Main.reside()を呼び出す必要はありません。

2010/08/17

文字列の全角・半角を変換する

NILScriptではJavaScript標準の型である文字列や数値にも拡張メソッドが用意されています。
文字列の拡張メソッド「format()」では、引数で指定した条件に従って文字列の全角・半角などの変換を行なえます。
引数には、今の所以下のアルファベットの組み合わせを指定できます。
a 全角→半角(アルファベットと数字)
A 半角→全角(アルファベットと数字)
r 全角→半角(アルファベット)
R 半角→全角(アルファベット)
n 全角→半角(数字)
N 半角→全角(数字)
s 全角→半角(空白文字)
S 半角→全角(空白文字)
m 全角→半角(対応する半角文字がある全ての記号)
M 半角→全角(全ての記号)
E 半角→全角(ファイル名に使用できない記号)
f 全角→半角(ファイル名に使用可能な記号)
Y MもしくはEと共に指定すると、\を/でなく¥に置換
k 全角→半角(カタカナ)
K 半角→全角(カタカナ)
V 半角→全角(カタカナ。濁点・半濁点の合成を行う)

これを利用すれば、何故か英数字が全角になっているサイトから抽出した文字列を半角に修正するなどの処理が簡単に行なえます。
例えば、「title」という変数に入っている文字列の全角文字を半角文字に置き換えるには、以下のようにします。
title=title.format('asm');
これだけで、「NILScript」のような忌々しい全角文字列を「NILScript」のように半角に変換できます。
全角英数字は、検索で見つけにくくなるなどの害がありますので、是非この機能を活用して撲滅してください。

ファイル名に使用できない文字だけを全角に変換する機能も便利です。
下記のようにすれば、ファイル名に使用可能な記号は半角に、使用不可能な記号は全角に変換してくれます。
var filename=title.format('asfE');
Webページのタイトル等から取得した文字列には、ファイル名に使用できない文字が含まれている可能性があるので、保存ファイル名などとして使う場合には、このようにして変換すると良いでしょう。

クラスの定義方法

NILScriptには、Javaなどのような「クラスベース」のオブジェクト指向プログラミングを行うための機能が用意されており、プロトタイプベースのオブジェクト指向機能しか用意されていない素のJavaScriptよりも簡単に、クラスのような物を作成できます。
ここでは、NILScriptでのクラスの作成方法を説明します。
NILScriptに同梱の「doc\base_class.txt」にも、細かな機能の解説が用意されているので、併せて参照してください。

クラスを定義するには、下記のように「new Class()」の返り値を変数などに代入します。
var SomeClass=new Class({
 __class__:{
  __call__:function(){
   println("called as a function");
  },
  someClassMethod:function(){
  },
 },
 __new__:function(){
  println("initializing");
 },
 someMethod:function(){
  println("do something");
 },
 someProperty:"defaultValue",
 get somePropertyWithAccessor(){
 },
 set somePropertyWithAccessor(){
 },
});
引数となっているのが、インスタンスのメンバを定義したオブジェクトです。
「__new__()」メソッドは、「new SomeClass()」のようにしてクラスのインスタンスを生成する時に実行されるコンストラクタとなります。
「__class__」には、クラス自体のメンバを定義したオブジェクトを指定します。このメンバは、インスタンスを生成することなく「SomeClass.someClassMethod()」のようにして使用できます。
特殊なクラスメンバである「__call__()」では、クラスが「new」を伴わずに通常の関数として呼ばれた時に実行される処理を指定できます。
その他、関数メンバであるメソッドや、単なる値のプロパティ、getter/setterによるプロパティも定義できます。
「__new__」「__class__」「__call__」などは、不要なら定義しなくても構いません。

別のクラスの機能を継承した派生クラスを作成することも可能です。
継承を行うには、下記のようにnew Class()の第1引数に継承元となるクラスを指定します。
この場合、クラスに追加・上書きするメンバは、第2引数で指定します。
var SubClass=new Class(SomeClass,{
 __class__:{
  someClassMethod:function(){
  },
 },
 someMethod:function(){
 },
});
なお、この継承はJavaScriptのプロトタイプチェーンを利用しているため、「(new SubClass()) instanceof SomeClass」のような式はtrueになります。
派生クラスのインスタンスではなく直接のインスタンスかどうかを調べたい場合は、「someObject.constructor===SomeClass」のようにするとよいでしょう。

更に、継承とは別に他のクラスの機能を一括して組み込む「ミックスイン」という機能も用意されています。
ミックスインを利用するには、第2引数以降にミックスしたいクラスを指定します。
他のクラスを継承させずにミックスのみを行いたい場合は、以下のように第1引数を「{}」にするとよいでしょう。
var SomeMixedClass=new Class({},SomeMixin,{
});
どんなクラスでもミックスできますが、通常はミックスインとして使うことを意図して用意されたクラスをミックスします。
NILScriptには、標準で「EventMixin」や「StreamMixin」など、よく使う機能を定義したミックスイン用クラスが用意されているので、是非活用してください。

2010/08/16

同期的にイベントを待機するwait()メソッド

NILScriptでは、クリップボードやファイルなど、イベント機構を備えたクラスは全て「observe()」というメソッドでイベントハンドラ関数の登録を行うようになっています。
これは、イベント機構で必要となる機能が「EventMixin」というクラスにまとめて実装されており、ミックスインという仕組みで各クラスに組み込まれているためです。

EventMixinには、他にも「wait()」などのメソッドが用意されており、EventMixinがミックスされている全てのクラスで使用できます。
wait()メソッドは、第1引数に指定された名前のイベントが発生するのを待ってから処理を返します。第2引数でタイムアウトを指定した場合は、指定ミリ秒数が経過したら待機を打ち切り、イベントが発生したかどうかを返り値とします。

この機能を使用すれば、イベントが発生するのを待って次の処理に進むというスクリプトが、下記のようにシンプルに記述できます。
var f=Main.directory.file("readme.txt");
println("readme.txtが更新されるのを待っています");
f.wait("modify");
println("更新されました");

バッチ処理的なスクリプトを書く時には特に便利ですので、覚えておくとよいでしょう。

2010/08/15

ファイルの作成・更新・削除を監視

2010/08/15版以降のNILScriptには、ファイルの変更を監視してイベントハンドラ関数を実行する機能が用意されています。定期的にファイル情報を取得するのではなく、OSのファイル更新イベント通知機能を使用しているので、システムへの負荷は少なくて済みます。
この機能を利用すれば、ファイルの作成などでしか処理の完了を検出できないソフトを含む作業の自動化などを簡単に実現できます。

特定のファイルの作成・変更・削除を監視するには、Fileクラスのインスタンスオブジェクトの「observe()」メソッドを使います。
Main.createNotifyIcon();
var f=new File("E:\\work\\_devel\\ng\\test\\test");
f.observe(['create','remove','modify','renameTo','renameFrom'],function(o){
 Main.notifyIcon.showInfo(o.event+":"+(o.file||""));
});
「Main.createNotifyIcon()」は、NILScript標準のタスクトレイアイコンを作成するメソッドです。Fileオブジェクトのobserve()を使うとスクリプトが常駐状態になるので、スクリプトを手動で終了させるためにトレイアイコンを用意しておく必要があります。
2行目では、パスを指定してFileオブジェクトを生成しています。
3行目が、イベントハンドラの登録処理です、observe()の第1引数には監視するイベント名を指定しますが、配列を指定すれば複数のイベントに対していっぺんにイベントハンドラを登録できます。ここでは、用意されている全てのイベント名を指定しています。
イベントハンドラ関数の中では、第1引数に与えられるオブジェクトの「event」メンバでイベント名を、「file」メンバで「renameTo」「renameFrom」のリネーム後・前のファイルのFileオブジェクトを参照できます。
「Main.notifyIcon.showInfo()」は、タスクトレイアイコンのバルーンでテキストを表示するメソッドです。

ディレクトリ内の不特定多数のファイル・ディレクトリの作成・変更・削除を監視するには、Directoryオブジェクトの「observe()」メソッドを使用します。
var d=new Directory("E:\\work\\_devel\\ng\\test");
d.observe(['add','remove','modify','rename'],function(o){
 Main.notifyIcon.showInfo(o.event+' '+(o.old||"")+' => '+o.file);
});
使い方はほとんどFileオブジェクトのobserve()と同じですが、用意されているイベント名が一部異なります。
また、作成・変更・削除されたファイルが、引数オブジェクトの「file」メンバで、renameイベントでのリネーム前のファイルが「old」メンバで取得できます。

これらの監視機能を使用する上で注意すべきなのは、ファイルの作成イベントが発生した時点では、まだファイルへの書き込みが行われている可能性があると言うことです。書き込み途中のファイルを操作しようとしても、ファイルを開けず失敗してしまったり、中途半端な内容しか読み取られなかったりするでしょう。
これを回避するには、ファイルのパスと最後に更新イベントが発生した時間をオブジェクトに格納しておいて、一定時間更新が発生していないファイルを書き込み完了とみなして操作するなどの工夫をするといいでしょう。

コマンドラインでのスクリプト指定が不要なEXEを作成

NILScriptでは通常、コマンドライン引数で実行させたいスクリプトファイル名を指定しなければならないため、Mozilla Firefoxの設定の「プログラム」の項での「他のプログラムを選択」のように、EXEファイルパスしか指定できない外部プログラム呼び出し機能からは、スクリプトを実行させられません。
この問題を解決するために、コマンドラインでスクリプトファイル名を渡さずに一定のスクリプトを実行するというEXEファイルを作成する機能が用意されています。

この機能を利用するには、NILScriptに同梱の「mkexe_c.bat」か「mkexe_w.bat」を実行し、表示されるプロンプトで生成したい実行ファイルの名前を指定してください。「_c」の方はコンソール版、「_w」は非コンソール版の実行ファイルが作成されます。

すると、NILScriptのディレクトリ内に、入力した名前で拡張子「.exe」のファイルと「.js」のファイルが作成され、拡張子「.js」のスクリプトファイルがメモ帳で開かれます。(使用するエディタはmkexe.ngを編集すれば変更できます。)

スクリプトファイルの内容は以下のようになっており、「/* script body */」と書かれている箇所に実行させたいスクリプトを記述します。
__NG__EvalFile(__NG__HostPath().replace(/([^\\]+)$/,"lib\\Base.ng"));
Main.main=function(){
 /* script body */
 
}
Main.execute();
後は、EXEファイルの方を実行すれば、記述したスクリプトが実行されます。
なお、実行ファイルに渡されたコマンドライン引数は、スクリプト中で「Main.params」というプロパティで配列として参照できます。

実は実行ファイルの部分はng.exe/ngw.exeと全く同じなのですが、コマンドラインの第1引数のスクリプトを読み込んで実行するという処理が定義されている「Main.main()」を上書きすることで、コマンドライン引数を使用せずに任意の処理を実行させられるのです。

2010/08/14

HTTPDのプロキシサーバ機能

NILScriptのHTTPDには、プロキシサーバとして動作してブラウザからのリクエストに応答し、コンテンツを書き換えたりするという機能も用意されています。
プロキシ機能を使用するには、HTTPD初期化時の引数オブジェクトに、以下のようなメンバを指定します。
後は、ブラウザで「プロキシ自動設定スクリプト」などの欄に「http://localhost/__proxy__/conf.pac」を指定すれば、「useProxy」の条件に一致し「noProxy」に一致しないURLへのアクセス時に、NILScriptのHTTPDのプロキシ機能を経由してアクセスされます。
実際の例はNILScriptに同梱の「sample\HTTPD.ng」を参照してください。また、メソッドなどの詳細はNILScriptに同梱の「doc\HTTPD.txt」で説明されています。
 useProxy:"http://",
 noProxy:"http://localhost/",
 proxyRequest:function(c){
  println(c.method+' '+c.url);
  if(c.url.host=='lukewarm.s151.xrea.com'){
   c.addScripts(function(){
    TestPlugin.serverSideTest(['proxy @ '+location.href])
   });
   if(c.url.path.match(/\.png$/)){
    c.cache=Main.scriptDirectory.directory('cache');
   }
  }
 },
 proxyResponse:function(c,r){
  if((c.url.host=='lukewarm.s151.xrea.com')
      &&(c.url.path.match(/\.dat$/))){
   r.lines.invoke('split',/<>/).addIndex()
    .execute(function([i,[name,mail,date,message,title]]){
    if(i==0){
     c.beginHTML(title);
    }
    c.write('<h3>'+(i+1)+': '+name+'('+mail+') - '+date
      +'</h3><p>'+message+'</p>');
   });
   c.endHTML();
  }
 },
プロキシとしての動作を指定しているのは、「proxyRequest」と「proxyResponse」の2つのメンバです。

proxyRequestの関数は、プロキシのリクエストを受取った時点で呼ばれます。
第1引数には通常のコンテンツ生成関数と同じようにConnectionオブジェクトが与えられ、その機能でリクエスト情報の取得やレスポンスの送信などを行えます。
また、HTML中に任意のスクリプトを挿入する「c.addScript()」や、テキストやHTMLノードに対するフィルタリングを行うメソッド群など、プロキシハンドラ専用の機能もいくつか用意されています。
addScript()で埋め込めるスクリプトでは、HTTPD上に定義したサーバ側関数なども利用できます。

「c.cache=」のようにすれば、レスポンスボディを保存するキャッシュファイルを指定できます。この場合、キャッシュファイルがあれば本来のリクエストを行わずにそれを返し、無ければ受信しながらキャッシュファイルとして保存するという動作になります。
また、「c.error(404)」のようにして、本来のリクエストを行わずにエラーを返したり、「c.write()」などで自前のコンテンツに差し替えてしまうことも可能です。

proxyRequestの中で送信が行われなかった場合、リクエストに従ってアクセスを行いレスポンスヘッダの取得までが行われ、proxyResponseで指定した関数が呼ばれます。
この時、第1引数にはConnectionオブジェクトが、第2引数にはリクエストにしたがって行ったHTTPリクエストのResponseオブジェクトが与えられます。Responseオブジェクトの機能の詳細については、「doc\HTTP.ng」の方で説明されています。

ここでは、レスポンスヘッダを書き換えたり、エラー時に代替コンテンツを生成して返すなどの処理を行うといいでしょう。
また、第2引数のレスポンスオブジェクトからレスポンスボディを読み込んで、そこから抜き出した情報を元にレスポンスを生成することも出来ます。


プロキシ機能を利用すれば、ブラウザを問わずに利用できる様々なブラウジング補助機能を実現できます。
ただし、ブラウザの設定を変更してプロキシを設定しなければならないという難点があるので、できるだけ通常のコンテンツ生成関数を利用した方がいいかもしれません。
例えば、あるWeb掲示板の表示を見やすくする機能を作りたい場合は、ページを取得して見易いHTMLに加工して返すというコンテンツ生成関数をHTTPD上のURLに割り当て、プロキシ機能では掲示板のURLから用意したURLへのリダイレクトのみを行うようにしておくのです。
こうすれば、プロキシ機能を利用できなくても、HTTPD上のURLへ直接アクセスすれば、用意された機能を利用できます。

2010/08/13

ネイティブコード関数のスクリプトへの埋め込み

ネイティブコード関数呼び出し機能では、DLL内の関数だけでなく、任意のアドレスを関数として呼び出すことも可能です。16進数列を元にバイト配列を生成してポインタオブジェクトを返す「Hex.decode()」と共に使用すれば、スクリプト中に任意のネイティブコード関数を埋め込んで使用できます。
このテクニックは、バイト列の操作などの速度を要する低レベルな処理を自前で実装したい時に役立ちます。

埋め込むネイティブコード関数を脳内アセンブルで作るのは大変なので、C言語などのコンパイラが生成する中間ファイルを使用するといいでしょう。
VisualStudioでは、「.cod」という拡張子の中間ファイルとして、アセンブラや16進数列の機械語コードが出力されます。

下記は、Base.ng中で定義されているネイティブコード関数埋め込みの例です。
memIndexOf=(new CFunction(
 Hex.decode('55 8B EC (中略) E5 5D C3')
)).toFunction([Pointer,Int,Pointer,Int],Int)
ライブラリオブジェクトの「proc()」の代わりに、「(new CFunction(Hex.decode("機械語の16進数文字列"))).toFunction([引数の型],返り値の型)」のようにして、16進数文字列をバイト配列→関数ポインタオブジェクト→ラッパー関数と変換しています。

2010/08/12

ネイティブコード関数からのコールバック

DLLなどが提供するネイティブコード関数の中には、他の関数へのポインタを引数として受取る物があります。
NILScriptでは、このようなコールバックで使用するためにJavaScriptの関数をネイティブコード関数でラップする機能も用意されています。
ここでは、この関数エクスポート機能の使い方について説明します。

まず、最初に関数の引数と返り値の型を指定して、関数ポインタ型のクラスを生成し、変数に代入しておきます。
下記は、Windowユニットで使用されている例です。

var EnumWindowsProc=StdCallFunction.define([UInt,Int],Int);

引数の型には、WideStringやMBStringなどの文字列系の型や、「new Struct()」で生成した構造体型を表すクラスなどを指定する事も出来ます。
「StdCallFunction」の他にも、「CFunction」や「ThreadSafeStdCallback」、「ThreadSafeCallback」というクラスがあります。
StdCallが付いているのが呼び出し規約がstdcallのもので、それ以外がcdeclのものです。ThreadSafeの意味については、後で説明します。

次に、下記のようにしてこの関数型を引数に取るネイティブコード関数のラッパー関数を生成します。

var EnumWindows=user32.proc('EnumWindows',[EnumWindowsProc,UInt],Int);

その後、コールバック関数型を指定した引数にJavaScriptの関数を渡して、ラッパー関数を呼び出します。

var handles=[];
EnumWindows(function(handle,lp){
 handles.push(handle);
 return(1);
},0);

この例では、EnumWindows()が呼び出されている間に、存在するトップレベルウィンドウの数だけコールバック関数が呼び出されます。

なお、コールバックの呼び出しが、コールバック関数を与えた関数から返った後に実行される可能性がある場合、関数を直接渡してはならないことに注意してください。
このような場合、以下のように関数型クラスの「from()」メソッドにコールバック関数を渡して関数ポインタオブジェクトを生成し、コールバック関数の呼び出しが起こる可能性がある限り参照が維持されるように、変数やプロパティに代入しておく必要があります。

//定義部分(抜粋)
var LowLevelMouseProc=StdCallFunction.define([Int,UInt,UInt],Int);
var SetWindowsHookEx=user32.proc("SetWindowsHookExW"
      ,[UInt,LowLevelMouseProc,UInt,UInt],UInt,true);

//関数ポインタオブジェクトの生成とコールバックの登録
var func=LowLevelMouseProc.from(function(code,wp,LP){/*** 略 ***/});
var hHook=SetWindowsHookEx(14,func,Main.handle,0);

/* 略(コールバックが呼び出される可能性のある処理(GetMessage()の呼び出し)) */

//コールバックの登録解除と関数ポインタオブジェクトの解放
hHook&&UnhookWindowsHookEx(hHook);
func&&func.free();

こうしておかないと、JavaScript関数やラッパー関数のメモリが途中で解放されてしまい、参照エラーや予期せぬ動作を引き起こしてしまうからです。
コールバック関数の登録解除処理を実行し、コールバック関数が呼ばれる可能性が無くなったら、関数ポインタオブジェクトの「free()」メソッドを呼び出して、関数ポインタオブジェクトが指しているラッパー関数のメモリ解放などを行います。

最後に、「ThreadSafe」の意味について説明します。
ThreadSafeの付いていない種類のコールバック関数では、関数ポインタオブジェクト生成時のスレッドのスクリプト実行コンテキストが使い回されるため、別のスレッドから呼び出されると不具合が生じてしまいます。
一方、ThreadSafeの付いている種類のコールバック関数では、毎回スクリプト実行コンテキストを新規生成して、それを使用してエクスポートされた関数を呼び出すため、別のスレッドからも問題なく呼び出せます。
ThreadSafe版を単一スレッド上から呼び出しても問題はありませんが、呼び出し時に多くのコストがかかるので、なるべくならThreadSafeでない方を使うことが望ましいでしょう。
実際の所は、ThreadSafeのコールバック関数が必要になるケースはそれほど多くはなく、NILScriptの標準機能の中では、今の所Thread.create()でしか使用されていません。

構造体を使用したネイティブコード関数呼び出し

DLLが提供するネイティブコード関数の中には、複数の値からなるバイト列である「構造体」を使用する物も少なくありません。
ここでは、構造体を利用したネイティブコード関数呼び出しの方法を説明します。

下記は、構造体を利用したネイティブコード関数呼び出しの一例です。

var Point=new Struct({
 x:Int,
 y:Int,
});
var GetCursorPos=user32.proc("GetCursorPos",[Point],Int);
try{
 var point=Point.alloc();
 GetCursorPos(point);
 println([point.x,point.y]);
 println(Hex.encode(point));
}finally{
 free(point);
}

最初に「new Struct()」で構造体オブジェクトを生成して、変数に代入しています。引数には、構造体のフィールド名と値の型を表すオブジェクトのペアを列挙したオブジェクトを指定します。フィールド定義は、構造体内での並び順の通りに並べる必要があります。
ライブラリオブジェクトのproc()によるラッパー関数定義で、引数の型を指定する箇所に構造体オブジェクトを格納した変数名を記述すれば、その構造体へのポインタを引数として受取ることを指定できます。

tryブロック内が、構造体オブジェクトを利用した関数呼び出しを行う部分です。
構造体のバッファは、UIntなどのバッファの時と同じように、「alloc()」メソッドで確保します。
この返り値を引数にしてGetCursorPos()を呼び出すことにより、確保したバッファ内に値が書き込まれます。
「point.x」や「point.y」のように、構造体定義で指定したフィールド名と同じ名前のプロパティで、バッファ内の当該フィールドの値を取得できます。また、「point.x=100」のようにすれば、フィールドの値を書き換えることも可能です。
なお、「Hex.encode()」は、ポインタオブジェクトが確保しているバイト列を2桁区切りの16進数文字列として返すメソッドです。ポインタを利用した関数の動作確認をしたい場合などに役立ちます。

最後に、確保したバッファを解放するたに、finallyブロック内で「free()」関数を実行しています。使用する関数によっては、すぐにバッファを解放してはならない場合などもありますので、関数のドキュメントなどをよく確認してください。

ポインタを使用したネイティブコード関数呼び出し

DLLが提供するネイティブコード関数の中には、引数にポインタを指定して、返り値以外にも値を受け取れるようになっている物があります。このような場合は、引数の型を「Pointer」にしておいて、Intなどの「alloc()」メソッドで生成したバッファオブジェクトを渡してやる必要があります。

下記は、その一例です。

var GetComputerName=kernel32.proc('GetComputerNameW',[WideString,Pointer],UInt);
try{
 var buf=WChar.alloc(128);
 var size=UInt.alloc().update(127);
 println(GetComputerName(buf,size));
 println(size.item());
 println(buf.toString());
}finally{
 free(buf,size);
}

Unicode文字列バッファで値を受取るには、引数の型を「WideString」にしておいて、「WChar.alloc()」で確保したバッファを渡します。alloc()の引数には、確保する要素数を指定します。引数の型はWideStringですが、alloc()はWCharのメソッドである事に注意してください。
また、バイト配列やマルチバイト文字列の場合、引数の型を「MBString」にして、バッファの確保は「Byte.alloc()」で行います。
WideStringやMBStringは、「toString()」メソッドでゼロ終端文字列とみなしてJavaScriptの文字列値に変換できます。

「UInt.alloc()」では、1つのUIntバッファを確保しており、続く「update(127)」でバッファの内容を書き換えています。このメソッドは、メソッドを呼び出されたPointerオブジェクト自身を返します。
ポインタオブジェクトを引数として関数を呼び出した後、「item()」メソッドを使用すれば、バッファの内容を数値として取得できます。

最後に、確保したバッファを解放するため、「free()」という関数を呼び出しています。この関数は、引数がfree()メソッドを持つオブジェクトだった場合にそれを呼び出すという動作になっており、Pointerオブジェクト以外にもfree()メソッドを持つ様々なオブジェクトの解放処理をまとめて実行できます。
free()は、何らかの問題があって例外が投げられた場合などにも必ず実行されるように、try...finally文のfinally節で実行する必要があります。また、この方法を使えば、buf.toString()の値を関数の返り値として返したい場合などに、いちいち変数に代入してからbufをfree()したりする必要が無くなるという利点もあります。

なお、JavaScriptにはGCという仕組みが用意されており、文字列やオブジェクトなどの値はどこからも参照されなくなったら自動的に解放されるようになっていますが、NILScriptでは敢えてこの仕組みを使わずに、明示的に解放処理を記述するようになっています。
これは、JavaScriptには「クロージャ」という仕組みがあり、変数に代入した値がその変数を定義した関数やブロックを抜けてもずっと参照されたままの状態になってしまうことがよくあるからです。
これは、変数を参照可能な場所で生成された関数が他の場所から参照され続けていることによるものですが、イベントハンドラ関数などが多用されているNILScriptでは、この状態が発生しやすくなっています。
「buf=null」などとしてやれば参照が失われてGCの解放対象となりますが、それではfree()を呼び出すのと同じですので、より直接的なfree()による解放が採用されています。

DLLなどのネイティブコード関数の呼び出し

NILScriptの特徴の一つに、DLLなどのネイティブコード関数を呼び出すラッパー関数を生成する機能があります。
実はNILScriptの機能のほとんどは、この機能でWindowsのAPIやNILScriptに同梱したDLLの関数を呼び出すことで実現しています。
ここでは、ネイティブコード関数呼び出しの基本的な手順を説明します。
なお、ネイティブコード関数関連の機能の説明は「doc\base_native.txt」にありますが、不完全な部分などがありますので、libディレクトリ内のユニットスクリプトでの実際の使用例も参照してください。

まず、最初に「Library.load()」か「WinLibrary.load()」で、ライブラリの読み込みを行います。関数の呼び出し規約がcdeclのライブラリの場合はLibrary、stdcallならWinLibraryを使用します。
load()の引数にはDLL名を指定しますが、システムディレクトリか実行ファイルのあるディレクトリ以外にあるDLLの場合は、フルパスで指定する必要があります。ユニットスクリプトにDLLを同梱する場合は、ユニット初期化関数内での「this.directory.file()」でユニットのディレクトリ内のファイルを取得すればいいでしょう。

次に、ライブラリオブジェクトの「proc」メソッドでラッパー関数を生成します。
下記は、Mouseユニット内で定義されているラッパー関数定義の一例です。

var SetCursorPos=user32.proc("SetCursorPos",[Int,Int],Int);

user32というのはNILScriptにあらかじめ用意されているグローバル変数で、user32.dllのライブラリオブジェクトが格納されています。
proc()の第1引数は、DLL内に定義されている関数の識別名です。大抵は、C言語用ヘッダやドキュメントに書かれている名前と一致するはずですが、WindowsのAPIの中には、末尾に「A」や「W」が付く場合があります。
第2引数は、引数の型を表すオブジェクトの配列です。Int/UInt/Short/UShort/Char/UChar/Double/Floatなどのプリミティブ型の他、バイト配列へのポインタを表すMBStringや、Unicode文字配列へのポインタを表すWideStringなどが用意されています。プリミティブ型の引数には数値を、MBString/WideStringを指定した引数には文字列やnullを指定します。
第3引数には、同様にして返り値の型を指定します。

上記のようにしてラッパー関数を生成しておけば、「SetCursorPos(100,100)」のようにすることで、ラップした関数を呼び出せます。

ウィンドウへのファイルドロップを生成するdropFiles()

NILScriptのWindowユニットには、他プロセスの所有するウィンドウ上の入力欄の内容を操作したり、ボタンを押したりして自動操作を行う機能も用意されていますが、世の中にはエクスプローラなどからのドラッグ&ドロップでしかファイルの指定を受け付けてくれないソフトがあったりします。
そこで、ウィンドウへのファイルドロップをエミュレートする「dropFiles()」というメソッドが用意されています。
以下は、その使用例です。

var w=require("Window").Window.find("Notepad");
if(w){
 w.dropFiles(Main.script).activate(true);
}else{
 run('notepad "'+Main.script+'"');
}

最初の行で、ウィンドウクラス名が「Notepad」であるウィンドウを検索して、見つかればそのWindowオブジェクトを「w」変数に代入しています。
次のif文で、ウィンドウが見つかったかどうかに応じて分岐しており、見つからなかった場合は新規にnotepadにスクリプトファイルを与えて起動し、見つかった場合はスクリプトファイルのドロップをエミュレートした後ウィンドウをアクティブ化しています。
Main.scriptというのは、自プロセスで実行されているスクリプトファイルを表すFileオブジェクトです。

なお、dropFiles()には、配列で複数のファイルを渡したり、第2,第3引数でドロップされるX/Y座標も指定することも出来ます。また、ドロップするファイルはFile/Directoryオブジェクトではなく単なるフルパス文字列で指定しても構いません。

「開く」メニューなどによるファイル指定方法が用意されているソフトでも、ドロップで指定した方が1ステップで済んで便利ですので、自動化ツールを作成する時には是非dropFiles()を活用してください。

他プロセスのコマンドラインを取得

NILScriptのProcessオブジェクトには、他のプロセスのコマンドライン文字列を取得する「commandline」プロパティが用意されています。
これを使用すれば、特定のファイルを開いているプロセスを強制終了するなどの機能も簡単に実現できるでしょう。

Windowsには、他プロセスのコマンドラインを取得するAPIが用意されていないため、commandlineの取得は特殊な方法で実現されています。
自プロセスのコマンドライン文字列を取得するのに使う「GetCommandLine」というAPIの関数アドレスが、同一環境上の全てのプロセスで一定であることを利用して、「ReadProcessMemory」というAPIで対象プロセスのメモリ内容を取得し、そこからコマンドライン文字列が格納されているアドレスを辿って読み取るのです。
ただし、UAC環境下で別の権限で実行されているプロセスなどのコマンドライン文字列は取得できません。これはWindowsVista以降のタスクマネージャに追加されたコマンドライン文字列表示機能でも同様のようです。

値を列挙するジェネレータの使い方

NILScriptが提供するクラスには、SpiderMonkey1.7で追加された「ジェネレータ」というオブジェクトを返すプロパティやメソッドが数多く存在します。
ジェネレータとは、値を順に出力するオブジェクトです。

値を配列として返す場合と違い、最初に全ての要素を生成する必要がないので、値が列挙されるごとに画面表示を行って体感速度を向上させられたり出来ます。
また、条件に一致する値を一つだけ検索する場合などに、要素が見つかった時点で値の列挙を打ち切ることも出来ます。

ジェネレータは、通常は下記のようにのようにfor...in文で使用します。

for(let p in Process.all){
 if(p.name=='ng.exe'){
  println(p.commandline);
 }
}

この例では、「Process.all」というプロパティで取得できるジェネレータが列挙するProcessオブジェクトをそれぞれ「p」という変数に代入して「{}」内の処理を実行します。
実行してみると、「ng.exe」で実行中のプロセスのコマンドラインが表示されるはずです。

しかし、NILScriptで用意されているジェネレータの拡張メソッドを使うと、ループを使用せずに様々な反復処理を効率よく記述できる場合があります。
例えば、前述の例は、拡張メソッドを使うと以下のように記述できます。

Process.all.filter('name','ng.exe').map('commandline').execute(println);

ここでは、最終的にグローバルに定義済みのprintln関数に渡されていますが、「execute(function(){})」のようにして任意の処理を実行させることも可能です。

なお、ジェネレータではなく配列が必要な場合などは「Process.all.toArray()」のようにすると、配列に変換できます。
また、逆に配列などをジェネレータに変換する「$G()」という関数も用意されています。

ジェネレータには、他にも多数の拡張メソッドが定義されており、同梱の「doc\base_stdex.txt」で説明されています。
全てを使いこなす必要はありませんが、filter()やmap()、execute()、toArray()などは、覚えておくと役に立つでしょう。

使い回したい機能をユニットスクリプトにする

大がかりなスクリプトをいくつか書いていると、前に書いた処理を使い回したくなることがあります。このような場合は、NILScriptにおけるライブラリである「ユニット」を作成しておいて、必要な場所で「require()」などで読み込んで使うようにすると良いでしょう。

ユニットスクリプトは、NILScriptの実行ファイルと同じディレクトリにある「lib」ディレクトリ内にディレクトリを作って、そこに作成します。
このディレクトリ内にあるディレクトリ名には「HTTP._test」のように、「.」に続いて謎の文字列が付加されていますが、これはユニットのバージョンを区別するものです。
「require("HTTP")」のようにバージョンの部分を省略して読み込んだ場合、辞書順で並び替えた時に一番後に来るバージョンのユニットが読み込まれます。バージョン部分も含めた名前を指定してrequire()を呼べば、任意のバージョンを使うことも可能です。
バージョンアップで仕様変更された時に、旧バージョンと新バージョンを共存させて使い分けたりするために、このような仕様が用意されています。
作成したユニットを公開するつもりが特になければ、バージョン部分は付けなくても構いません。

ユニットのディレクトリの中には、ユニットのバージョンを除く名前部分に拡張子「.ng」を付けたファイル名でスクリプトファイルを作成します。このスクリプトファイルには、ユニットの初期化を行う関数を記述します。
関数が呼び出される時のthisは、require()が返すUnitクラスのインスタンスオブジェクトです。基本的に、以下のようにしてクラスなどを定義し、ユニットのメンバとして公開します。クラスだけでなく、単なるオブジェクトや値、関数なども公開できます。

(function(){
 var MyClass=new Class({
 });
 this.MyClass=MyClass;
})

一旦varで変数に代入してからthisのメンバに代入しているのは、ユニットスクリプト内の処理からそのクラスを参照しやすくするためです。
クラスの定義方法については、NILScriptに同梱の「doc\base_class.txt」や、同梱のユニットスクリプトの実例を参照してください。

なお、ユニットスクリプトに記述された関数内では、「this.directory」でユニットスクリプトのあるディレクトリを表すDirectoryオブジェクトを参照できます。
これを使用することで、ユニットに同梱したファイルのフルパスを得たり、内容を読み込んだり出来ます。
例えば、「HTTPD」ユニットでは、スクリプトの最初の方で「var jQuery=this.directory.file('jquery.js').load();」として、ページ中に埋め込むjQueryのスクリプトを読み込んでいます。
File/Directoryオブジェクトの使い方については、NILScriptに同梱の「doc\base_io.txt」を参照してください。

Window.observe()でウィンドウの表示などに応答

NILScriptでは、「Window」ユニットに用意されている機能を使って、自前のGUIウィンドウを表示したり、他のプログラムの持つウィンドウを操作したり出来ます。

多くの機能が用意されているWindowクラスですが、中でも特徴的なのはウィンドウの生成や表示を監視してコールバック関数を実行してくれる「Window.observe()」でしょう。
この機能では、イベントの発生をシステムから通知してもらうシェルフックという仕組みを使用しているため、ループやタイマーでウィンドウの状態を監視するのに比べて低負荷で素早い応答が可能になっています。

次のスクリプトは、Window.observe()を使用したスクリプトの例です。
ここでは、ウィンドウの表示を監視し、条件に一致するウィンドウだったらそのウィンドウを閉じるという処理を行っています。
==の後の文字列は、それぞれ実行ファイル名とウィンドウタイトルの条件を指定しています。これを変更すれば、不要なダイアログなどを自動で閉じるツールとして使えるでしょう。

Main.createNotifyIcon();
var Window=require('Window').Window;
Window.observe('show',function(e){
 if((e.window.ownerProcess.name=='notepad.exe')&&(e.window.title=='行へ移動')){
  e.window.close();
 }
});

Window.observe()の第1引数はイベント名で、以下の物を指定できます。

activate
新たなウィンドウがアクティブになったとき。
create
ウィンドウが生成されたとき。
destroy
ウィンドウが破棄されたとき。
show
表示されたとき。なお、ツールチップなどのウィンドウでも発生する。
hide
非表示になったとき。
redraw
タイトルバーの内容が変更されたときなど。変更前と同じ内容の時もある。

第2引数には、イベントが発生した時に実行されるコールバック関数を指定します。
この関数の第1引数には、「window」メンバにイベント発生元のウィンドウを表すWindowオブジェクト、「event」メンバに発生したイベント名が格納されたオブジェクトが渡されます。
Windowオブジェクトに用意されている機能については、NILScriptに同梱の「doc\Window.txt」を参照してください。

2010/08/11

ユニット(拡張ライブラリスクリプト)の読み込み方法

NILScriptでは、主要な機能以外の多くの機能は「ユニットスクリプト」というライブラリになっており、実行時に必要になった時点で動的に読み込んで使うようになっています。
ユニットというのはDelphiに倣った呼称で、「ライブラリ」や「モジュール」にしなかったのは、DLLなどと混同されないようにするためです。
今回は、ユニットで定義されているクラスを利用するためのいくつかの方法について説明します。

最も推奨される方法は「require()」です。この関数は、引数で指定したユニットを読み込んで、Unitクラスのインスタンスオブジェクトを返します。ユニットが提供するクラスなどは、このオブジェクトのメンバとして格納されています。
なお、同じユニットを複数回require()した場合、読み込みや初期化が行われるのは最初の1回だけで、以降は1回目の返り値が使い回されます。
ユニットが提供するクラスを複数回使用する場合は、このオブジェクトを変数に代入しておくとより効率的ですが、1回しか参照しない場合は「require("HTTPD").HTTPD.create(/*...*/);」のように、直接配下のクラスの機能を呼び出しても構いません。
提供されるクラスが1つだけのユニットを使う時や、使いたいクラスが1つだけの場合は、「var Clipboard=require("Clipboard").Clipboard;」のように、使いたいクラス自体を変数に代入してもいいでしょう。
また、分割代入という方法を使えば、「var {Keyboard,Key}=require("Keyboard");」のようにして、複数のクラスをローカル変数に代入することも可能です。
ユニットで定義されている名前とは別の名前の変数に代入したい場合は、「var {Keyboard:KB,Key}=require("Keyboard");」のようにすることも出来ます。この場合、Keyboardクラスは「KB」というローカル変数に代入されます。

一方、最も手軽な方法は、「use()」です。こちらは、引数で指定した名前のユニットで提供されているメンバをグローバル変数として展開します。
use()は手軽な反面、既存のグローバル変数を上書きしてしまうので、注意が必要です。
ユニットが提供するクラスの中には、標準状態でグローバルに定義されているクラスや、他のユニットから展開されたクラスと同じ名前のクラスが存在する可能性があるからです。
use()は、数行程度の簡単なスクリプトでしか使わない方がよいでしょう。特に、自作のユニットスクリプトの中で使うのは絶対に避けてください。

ユニットが提供するクラスを、グローバル変数としてではなくローカル変数として展開する方法として、「eval(Unit())」という書き方も用意されています。
「Unit」を関数として呼び出すと、引数で指定した名前のユニットを読み込んで各メンバをローカル変数に代入するという内容のスクリプト文字列が返されます。これをeval()することで、その場所のローカル変数に展開するのです。
この時、「var」による関数スコープではなく、「let」によるブロックスコープが使用されます。
既存のグローバル変数と重複する名前のクラスが定義されているユニットを手軽に使いたい場合などに利用するといいでしょう。
ただし、何のブロックの内側でもないトップレベルで使用すると、グローバル変数を上書きしてしまうので注意してください。

また、応用的な方法として「with(reqiure()){/*...*/}」という書き方も出来ます。
この場合、withブロックの中ではrequire()が返したUnitオブジェクトのメンバをローカル変数のように利用できます。
比較的手軽で安全なので、覚えておくとよいでしょう。

ClipboardでHTML形式を読み書きする

NILScriptには、クリップボードの読み書きを行うClipboardユニットが用意されています。
Clipboardユニットでは、単なるテキスト形式以外にも、ブラウザ上のWebページをコピーした時に格納されるHTML形式や、エクスプローラなどでファイルをコピーした時の形式なども操作できます。
これらの中でも特に役に立つのが、HTML形式の読み書きでしょう。

Webページの内容をコピーしてテキストエディタに貼り付けた場合、テキストとして表示されていなかったリンク先のURLなどは失われてしまいますが、ClipboardユニットのHTML形式読み取り機能を利用すれば、コピーされたHTMLを文字列として読み取ることが出来ます。
例えば、Webページ上のリンク集などをコピーした状態で以下のような式を使用すれば、コピーされたHTMLからリンクされていたURLを配列として抽出できます。

require("Clipboard").Clipboard.html.html.grep(/<a [^>]*href="([^"]*)"/i).map("1").toArray()

これを応用すれば「コピーした範囲にあるリンクを全て開く」などの機能を実現できます。
なお、grep()やmap()などは、SpiderMonkeyの「ジェネレータ」の拡張機能としてNILScriptで用意されているものです。詳細はNILScriptに同梱の「doc\base_stdex.txt」を参照してください。

また、以下のようにすれば、指定したHTMLをHTML形式としてクリップボードにコピーすることも出来ます。


require("Clipboard").Clipboard.html="<b>NILScript</b>"

これをブログの投稿画面などのWYSIWYG編集欄や、一部のワープロソフトなどに貼り付ければ、HTML内の文字装飾やレイアウト、リンクなどが反映されます。

これらの機能をHotstrokesなどと組み合わせて利用すれば、Webブラウジングや文書の編集などを効率化させられるでしょう。


真のマルチスレッド

NILScriptでは、HTTPDの応答処理やキー・マウスのフックなど、様々な処理がマルチスレッドで並列実行されるようになっています。
また、Thread.create()というメソッドを使用すれば、任意の関数を新規スレッドで実行させられます。
これにより、HTTPの通信など時間のかかる処理を行っている間に、その処理の完了を待つ必要がない他の処理を進めたりできます。

スクリプト実行環境のマルチスレッドというと、スクリプトエンジンが独自に複数の処理を切り替えながら少しずつ実行することで並列実行されているように見せる疑似マルチスレッドと、OSのスレッドの仕組みを利用して並列実行を行う真のマルチスレッドなど、いくつかの方式がありますが、NILScriptでは真のマルチスレッドが使用されています。
これは、NILScriptで利用しているJavaScriptエンジンのSpiderMonkeyが、いくつかの点に注意するだけで簡単に真のマルチスレッドでの利用が出来るようになっているおかげです。

NILScriptでは、多くの処理を外部のDLLが提供する関数を利用して実現していますが、そんな関数の中には処理に時間のかかる物も少なくありません。もし疑似マルチスレッドしか用意されていなかったら、データの読み込みなどを行っている間にキーボードフックの応答処理が実行できずに操作不能になるなど、様々な問題に見舞われることでしょう。
こうした問題に悩まされることなく様々な機能を実現するためには、真のマルチスレッドで実行できることが非常に重要なのです。

真のマルチスレッドには、マルチコアCPUなどの性能を引き出せるという利点もあります。実際のスレッドが一つしかない疑似マルチスレッドでは、一度に使われるコアは一つだけになっていまうので、デュアルコアCPUであれば50%までの性能しか引き出せませんが、各スレッドを別のコアで並列実行できる真のマルチスレッドでは、100%の性能を引き出せる場合もあるのです。
SpiderMonkeyでは、使われなくなったデータを解放するGCの際に他のスレッドを一時停止してしまうので、スクリプト部分での処理が多いとあまり高速化できない場合がありますが、外部DLLに画像処理などの時間がかかる処理をさせる場合は、CPUのコアの数だけスレッドを生成して並列実行させることで、CPUの性能の限界まで高速実行させられるでしょう。

キーボード・マウスのフック

Hotstrokesの内部処理の内、キーボードやマウスを扱う部分は「Keyboard」「Mouse」というユニットで定義されており、それぞれ単独で利用することも可能です。

これらのユニットでは、ユーザーの操作がシステムに受理される前に特定の処理を呼び出して、何らかの処理を行ったり操作の受理をブロックすることなどが出来る「フック」という仕組みを利用する機能が提供されています。
これらの機能を利用すれば、あらゆるキー操作に一括してある処理を割り当てたり、マウスの移動が発生するごとに処理を実行させるなど、Hotstrokesでは出来ないようなスクリプトも実現可能です。

このようなキー・マウスフックの機能を標準で利用できるスクリプト実行環境はあまり多くありませんので、フックを用いたツールを手軽に自作したい人などは、NILScriptを試してみるといいでしょう。
NILScriptに同梱の「sample」ディレクトリには、全てのキー操作の情報をコンソールに逐次出力する「keyboard_hook.ng」や、マウスが移動した時にその操作を無効にして上下左右が逆の操作を発生させる「sample/mouse_hook.ng」などのスクリプト例が用意されているので、参照してみてください。

なお、これらの機能を利用したスクリプトでは、あらゆる操作が不能になってしまうような危険なバグが発生する可能性があることに注意してください。
仮想PCやテスト用のPCなど、再起動する羽目になっても良いような環境で十分にテストをすることが望ましいでしょう。

2010/08/10

Hotstrokesのプラグイン機構

Hotstrokesでは、プラグイン機構によりキー名の定義などを追加することも可能になっています。
標準では、マウスのボタン操作やカーソル移動をストロークとして扱うプラグインなどが用意されており、マウスジェスチャ的な操作への動作割り当ても可能です。
その他、プラグインを作成すれば、タブレットやジョイスティックなどのデバイスの操作を独自のキーとして扱うことも可能になるでしょう。

更に、プラグイン機構では、条件別割り当てで使用する条件や、動作割り当ての動作として定義する処理なども定義できるようになっています。
特定のソフトに関する条件や動作の定義をまとめたプラグインを作成して公開しておけば、他の人はそれを導入して操作・条件・動作の組み合わせを記述するだけで簡単に望みの機能を利用できるはずです。
Hotstrokesでは、難しい定義をなるべく簡単に記述できるようにするような配慮は特に行われていませんが、これは条件や動作の定義は十分にスキルのある人がプラグインとして公開することを想定しているからです。

マルチストローク対応ホットキー定義機能「Hotstrokes」

NILScriptの機能の中でも比較的初期に実装され、代表的な機能であると思われている感があるのが「Hotstrokes」ですが、実はドラゴンボールの技に例えると「太陽拳」くらいの、たまに活躍するけどそれほど重要ではないという感じのポジションにあったりします。
しかし、せっかく作ったのですから、主な特徴について紹介しようと思います。
実際の使用例や詳細な仕様については、NILScriptのアーカイブに同梱されている「sample\Hotstrokes.ng」や「doc\Hotstrokes.txt」を参照してください。

Hotstrokesの基本的な機能は、特定のキー操作が行われた時にスクリプトで定義された処理を実行する「ホットキー」的なものですが、複数のキー操作からなるストローク列に対する動作割り当てを簡単に記述できるようになっているのが特徴です。
複数のキー操作というのは、例えばEmacsなどで見られる「C-x C-c」のような2ストロークキーや、「Ctrlキーの2連打」などのようなものです。また、あるキーを押してから他のキーを押さずに放すタップ操作なども、「押す」と「放す」の2つの操作からなる複数ストローク操作と言えます。(通常のホットキーは、押し下げた瞬間に発動するのが一般的です。)
その他、各ストロークを一定時間内に行ったり、「長押し」など一定時間待機してから続きの操作を行うなどの操作も、"Ctrl+C"のような基本的なホットキー定義と同じように、一つの定義文字列で指定できます。

また、既存のキーをCtrlやShiftのようなモディファイアキーとして再定義することも可能です。「mod1」のような同名のキーをいくつでも定義できる他、単に押してからすぐ放した時は本来のキーの動作を発生させる「ワンショットモディファイア」などと呼ばれる機能も簡単に定義可能です。

操作が行われた時に実行される動作の定義には、NILScriptの関数を指定できるので、ウィンドウを閉じるなどの操作をしたり別のキー操作を発生させるだけでなく、様々な動作を定義可能です。
また、アクティブウィンドウの種類などの条件に応じて動作を変える条件別割り当ても可能です。条件もNILScriptの関数で柔軟に定義可能です。

「谺の隠れ家(http://echokoda.y0r.net/archives/category/soft/nilscript)」というサイトでは、Hotstrokesを利用して「下駄配列」や「月配列」などの仮名入力を定義するスクリプトが公開されているので、Hotstrokesの応用例に興味がある人は是非参照してみてください。

スクリプト編集に使うテキストエディタ

NILScriptのスクリプトは、テキストエディタであればWindows標準のメモ帳でも作成できないことはありませんが、より高機能なテキストエディタを使用した方が快適です。

NILScriptの作成には、「VxEditor(http://dr-x.jimdo.com/)」というエディタが使用されています。
「{」の直後で改行したり行頭で「}」を入力することで自動的にインデントを行うスマートインデントや、3カテゴリまでのキーワード群を定義可能な色分け表示など、プログラミングを快適にする機能が多数用意されています。

また、JavaScriptに似たDMonkeyというスクリプト言語を利用したマクロ機能が搭載されており、様々な機能を追加可能です。
特に、「VxEditor & DMonkey布教ページ(http://lukewarm.s41.xrea.com/)」の「VxEditor用DMS」で公開されている「dabbrev」や「インクリメンタルサーチ」、「VxEditor & DMonkey関連アップローダー」の081番で公開されている単語ジャンプなどは、プログラミングの際に非常に役立っています。

これらのマクロの多くは、VxEditorとよく似たタブ型エディタの「JmEditor」でも使用できるので、高解像度のモニタでいくつものウィンドウを並べて編集したい人以外は、そちらを使うと良いかもしれません。

VxEditor & DMonkey布教ページ

さて、NILScriptでは、基本的な機能以外はユニットスクリプトとして分割されていますが、それぞれのユニットはどんなに長くても一つのスクリプトにまとめられ、細かい処理内容に応じて分割されたりはしていません。
これは、先に挙げたマクロの機能を最大限に活用するためです。
関連するコードが一つのファイルにまとまっていれば、インクリメンタルサーチや同一単語間ジャンプでどこにでも素早く移動できるのです。
また、テキスト中の単語からカーソル位置の単語に先頭一致する単語を列挙して候補として表示し省入力を実現するdabbrevでも、ファイルを分割しない方が補完できる単語が多くなります。

どこに何が書かれているかを知らない人にとっては、分割されていない長大なスクリプトファイルを読み解くのは大変だと思われるかも知れませんが、「new Class」を含む行を検索したりすれば、大まかな構成はすぐに把握できるでしょう。

HTTPD


NILScriptに用意されている機能の中で最も特徴的なのが、Webサーバ機能を実現する「HTTPD」です。
単にローカルにあるファイルを公開するだけでなく、リクエスト内容に応じて動的にWebページを生成して返すことも可能です。
また、ブラウザ上のJavaScriptからNILScript側の機能を呼び出すための機能も用意されており、HTMLによる自由度の高い画面表示とNILScriptの多彩な機能を連携させたアプリケーションを作成可能です。
ブラウザからアクセスして利用するため、設定画面などをブックマークしておいて素早く呼び出せるなど、通常のGUIアプリケーションでは出来ない使い方も可能です。

HTTPDの機能を試すには、NILScriptのアーカイブに含まれる「start_httpd.ng」を実行してください。サンプルのWebサーバスクリプトが起動し、ブラウザでドキュメントの検索・閲覧ページが表示されるはずです。
このスクリプトには、将来的には自作スクリプトの管理やアップデート情報の閲覧などの機能も追加され、NILScriptに関するポータルサイト的な物となる予定です。
HTTPDを利用したプログラムを作ってみたい場合は、アーカイブ内の「sample\HTTPD.ng」にあるサンプルスクリプト本体や、HTTPDのドキュメントを参考にしてください。
HTTPDによって公開されたドキュメント閲覧ページ
「Script console」のリンクからは、NILScriptのスクリプトを簡単に実行するためのコンソールページを表示できます。下部の入力欄にスクリプトを入力してボタンを押すと、入力されたスクリプトがHTTPDに送信され、実行結果(最後に評価された式の値)やエラーが返信され、ログとして表示されます。
スクリプトは専用のスレッド上で実行され、定義した変数などは次回実行時に引き継がれるので、対話型インタプリタのような感覚で利用できます。
簡単なコードを試してみたい場合などに役立つでしょう。
スクリプトコンソールのページ

このコンソールはHTTPDのプラグイン機構で実装されており、処理の本体は「plugins\HTTPD\ScriptEvaluator\ScriptEvaluator.ng」内に記述されています。スクリプト内容を見れば、NILScriptによってサーバ側関数の呼び出しがラップされて、HTTPのリクエストなどを意識することなく連携処理を記述できているのが分かるはずです。
また、スクリプト実行環境の部分はライブラリ化されており、「lib\ScriptEvaluator._test\ScriptEvaluator.ng」にあります。こちらでは、マルチスレッドでの同期処理機能などの使用例が見られます。

なお、NILScriptのHTTPDは、ローカルネットワーク上から1人もしくは小数のユーザーがアクセスして利用することのみを想定しているため、インターネット上に公開して不特定多数のユーザーに利用してもらう用途には向いていません。
これは、NILScriptではネットサービス上から必要な情報を収集して様々に活用するプログラム群の作成を優先的な目標としているためです。
わざわざネットサービス型のプログラムを作成するのは、不特定多数のユーザーの投稿を利用した集合知的な機能を実現したい場合か、広告を掲載して収入を得たい場合などが主だと思われますが、広告を無視して情報を抽出するツールを使用してアクセスする人が増えれば、サーバ負荷ばかり増えて収入は減ってしまいます。
そうなると、NILScriptのHTTPDに不特定多数からのアクセスを捌くための機能を充実させたとしても、大した使い道が無く無駄になってしまうでしょう。
そのような理由から、今後も大規模ネットサービス公開向けの機能が積極的に実装される予定はありません。どうしても欲しいという人は、自分で拡張ライブラリを作成してください。

NILScriptとは

NILScriptは、下記のURLで配布されているJavaScriptベースのスクリプト実行環境です。
テキストエディタなどでプログラムを記述し、保存したファイルパスをコマンドライン引数に与えて実行することで、様々な処理を実行させられます。

http://lukewarm.s151.xrea.com/nilscript.html

ファイルやテキストの処理と言った基本的な機能から、HTTP通信などのインターネット関連の機能、ウィンドウやキーボード、マウスの自動操作など、多くの機能が用意されています。
DLLなどの関数を呼び出したり、メモリ上に確保されたバイト列を操作する機能が用意されているので、標準では用意されていない機能も実現しやすくなっています。

NILScriptの最大の特徴にして長所と言えるのが、スクリプトエンジン部分にMozilla Firefoxなどに搭載されている「SpiderMonkey」というJavaScriptエンジンを採用していることです。
JavaScriptは、数あるスクリプト言語の中でも特にC言語に似た仕様を持つ言語ですので、JavaScript自体には馴染みがなくても、C++やJava、C#など、C言語風の構文を持つ言語の使用経験があれば、戸惑うことなく使用できるはずです。

NILScriptは、これからプログラミングを習得しようとしている人にもお薦めです。
テキストなどを表示するだけのプログラムではなく、自分が欲しいツールを作りながらであれば、飽きたり挫折することなく学習を続けられるはずです。
NILScriptでC言語風の構文に慣れておけば、C++やJava、C#などの言語で高度なプログラムを作りたくなった時にも、スムーズに習得できるでしょう。

一方、プログラミングの経験がなく、今後も本格的に習得するつもりは一切無いけれど、便利な機能を利用したいという人は、NILScriptよりも他のスクリプトユーティリティを使った方がいいかもしれません。
JavaScriptには、変数名などの識別子を大文字・小文字の別まで正しく記述しなければならなかったり、文の後に「;」を記述しなければならないなど、プログラミング未経験者にとって一見理不尽に面倒臭く感じられる仕様が少なくないのに対し、独自仕様のスクリプトエンジンを搭載したスクリプトユーティリティでは、こうしたプログラミング未経験者への配慮が手厚い傾向にあります。