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には、変数名などの識別子を大文字・小文字の別まで正しく記述しなければならなかったり、文の後に「;」を記述しなければならないなど、プログラミング未経験者にとって一見理不尽に面倒臭く感じられる仕様が少なくないのに対し、独自仕様のスクリプトエンジンを搭載したスクリプトユーティリティでは、こうしたプログラミング未経験者への配慮が手厚い傾向にあります。