SAORI/0.0
(Working Draft/20020130-01)

SAORIとは?

栞に対するバイナリ拡張の形式を標準化することで、機能拡張に対する栞開発者の負荷を軽減すると同時に、ゴーストデベロッパが、使用している栞に要求する機能が無いことを理由に、思いついたネタを諦めたり、もしくはそのためだけに栞を乗り換える必要をなくすためのものです。
関連スレッド:栞を考えるスレ(現在、議論中)

インデックス

名称

突如浮上した『さおり』に仮決定。特に異論がなければこのまま。 説明的な名称(「栞拡張プラグイン」とか)については引き続き。

basicとuniversal

SAORIの実装はbasicとuniversalの2つに分かれる。後者のほうが柔軟だが、実装に手間がかかる。

SAORIの実装

DLL形式のSAORI

Windows上においては、SAORIは原則的にDLLによって実装される。(EXE形式による実装に関しては後述)。

実行関数の方式

案1
HGLOBAL execute(HGLOBAL h, long *len);
GlobalAllocで確保されたメモリ領域に単一の文字列として引数その他の情報をセットして渡し、結果は、SAORI側でGlobalAllocされた領域に単一の文字列としてセットされ、そのハンドルが返り値になる。同時に引数で渡されたlong*の指す領域に文字列の長さをセットする必要がある。
問題点
文字列から引数をとりだすためのパーザが必要になる。栞・真琴・プラグイン等の開発経験者にとっては問題ないが、一からはじめようとする人間にはつらいかもしれない。
案2
HGLOBAL execute(HGLOBAL h);
案1の変形で、文字列をかならず0x00で終端することで文字列長の指定を不要にする。 メモリ領域に対する取り扱いは案1に準じる。
問題点
ほぼ1と変わらないが、加えて0x00を探す処理が必要なため多少とはいえ遅くなる、引数中に0x00を含めることができない。
案3
HGLOBAL execute(int argc, char* argv[]);
C/C++のmain()風。argcが引数の数を指し、argvにはchar*の配列に格納された引数本体がセットされる。返り値は0x00で終端された文字列が格納されたHGLOBAL。
問題点
結局のところ返り値側は書式化された文字列を返すか、もしくは単一のデータしか渡せないので中途半端。加えて、案2で挙げたの問題点とほぼ共通の問題がある。
案4
HGLOBAL execute(int* retlen, int argc, char* argv[], int[] argn);
案3に文字列の長さを加えたもの。
問題点
中途半端に冗長。案2で挙げたような問題点は解決されるが、果たしてそれに見合うコストなのか。
案5
int execute(HGLOBAL argc, HGLOBAL args, HGLOBAL argn);
//int execute(int* argc, char* args[], int argn[])
それ自体がGlobalAllocされたargc, args, argnにそれぞれ引数の数、引数の配列、引数の文字列長をセットして渡し、返り値も同じargc, args, argnにセットして返す。返り値に返り値の数をセットすればint* argcではなくint argcにできるが。
問題点
簡便に書けることが利点であるはずの案3を元にしていることを考えれば論外だろう。
案6
HGLOBAL execute(HGLOBAL args);
引数にはNULL終端されたchar*ポインタの配列が渡される。返り値はポインタに同じ形式で文字列を格納して返す。
問題点
冗談。たしかに引数は少ないがそれだけ。
総括
既に何か。本体が使用している案1を除くとDelphiとの親和性が不明。現実的にはWindows上のDLL開発に用いられるのはC/C++かDelphiが圧倒的だろうが、なるべく汎用性があったほうがいいだろう。
関数の形式
__cdeclか__stdcallか。
何か。用各種DLLの仕様に沿うなら__cdecl。

文字列引数の形式

引数、返り値を単一の文字列として渡す場合の形式。
案1
COMMAND subcommand version[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
[CRLF]

version statusnumber statusstring[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
[CRLF]
各行はCRLFで区切られ、一行目のみ半角スペースで区切られ、2行目以降は": "で区切られたKey,valueの対。
SHIORI/2.x、SSTP等で使われている形式。(pluginの場合subcomman部分が無い点が異なる)。それらに触れたことのある人間にとっては既存のソースを流用できる。
案2
COMMAND[0x01]subcommand[0x01]version[0x02]
Name[0x01]Value[0x02]
Name[0x01]Value[[0x02]
Name[0x01]Value[0x02]
[0x02]

version[0x01]statusnumber[0x01]statusstring[0x02]
Name[0x01]Value[0x02]
Name[0x01]Value[0x02]
Name[0x01]Value[0x02]
[0x02]
形式は案1に準じるが区切りとして0x01、0x02といった値を用いる。特にvalueの内容などと衝突する可能性が低くなるが、純粋な文字列とは言い難く、人によっては抵抗を感じる。
案3
Command: command[CRLF]
SubCommand: subcommand[CRLF]
Version: version[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
[CRLF]

Version: version[CRLF]
Status: number[CRLF]
Name: Value[CRLF]
Name: Value[CRLF]
[CRLF]
全体をkey: value[CRLF]形式に統一したもの。パーザの処理が統一されるという利点はあるが、一行目の時点では対応した形式であるという判断ができないのが欠点。
案4
Command: GET Version SHIORIPLUGIN/1.0[CRLF]
案1と案3の混合だが余計ややこしいだけだと思われる。

複数引数の並び

main()系の引数形式の場合の並び順。コマンドライン引数のように"-l"のような文字列を使って意味を識別するのは多分に冗長なので、できれば順番のみで識別できるようにしたい。

エラー処理

エラーの伝達方法
universalではレスポンスのステータスコードを使用すれば良い。
エラーの分類
呼び出しの不備
存在しないコマンドが呼ばれた。
引数の数、内容の不一致。
リクエストの内容が解釈不能
など。
SAORI側エラー
内部でエラーが発生した。
一時的な理由によるエラー
たとえばhttpアクセスを行うSAORIで対象にアクセスすることができなかった場合など。
栞側のエラー処理
これは、栞内部で処理するか、スクリプトにエラーを渡して判断させるか等は栞の裁量で決めて構わない。

SAORIの寿命

案1
SAORIのDLLは原則として栞のload時に読み込まれ、栞のunload時に破棄される。
案2
必要時にのみ読み込まれ、処理終了後破棄される。
総括
案1の場合、多数のSAORIを使用した場合メモリを大量に要求し、 案2の場合、長期的な状態を保持することが困難である。
折衷案として、長期的な状態を保持することが必要なSAORIは案1、そうでないものは案2を取るのが現実的かもしれない。
また、保存したいデータを返り値に含めて渡し、呼び出し側の栞等で保存するという手法はどうだろう?(これはEXE形式のSAORIでも用いることができるかもしれない。)

返り値

SAORIから栞に返される値の定義。形式自体は引数の形式に大きく依存することになるだろう。
形式
栞のようにステータスコードを返すか?
返す場合、どういった意味のものを用意するか。
また、Key: value形式の場合、どういったKey名を使用するか。
複数返り値のサポート
複数返り値のサポートを仕様に含めるか?
とりあえず仕様で規定しておくにこしたことはない。
栞側が複数の返り値に対応しない場合(栞の固有スクリプトにおいて配列変数等が使用できない)場合の扱い。
1.栞側がどちらを要求しているかをSAORI側に伝え、それによって単数/複数を切り替える
2.常に単数/複数の双方を返す。

非同期SAORI

非同期SAORIに必要な仕様。

EXE形式による実装

EXE形式でSAORIを実装する場合、Proxy DLLを経由する。 Proxy DLLはEXEによるSAORIを実行し、その結果を返す。結果はDLLを経由して栞に渡される。引数と標準出力のみを扱えばいいので敷居が低いと思われる。と同時に、外部プロセスとして実行されるため、こみいった処理や非同期的な処理に適しているかもしれない。

SAORIの定義ファイル。

SAORIを呼び出す栞に情報を渡すために、一定の書式のテキストファイルが用意される。
内容はファイル配置に依存しそうなので、まとめて。

SAORIのファイル配置

試案1

home
 +-ghost
    +-[Name]
        +-ghost
           +-master
              +-shiori.dll
              +-SAORI.txt
              +-md5.dll
              +-proxy.dll
              +-proxy.ini
              +-serch.exe
              +-update
              |  +-update.exe
              |  +-tmp
              |  |  +-tmp.dat
              |  +-work.lst
              +-pinball
                 +-pinball.dll
                 +-pinball.ini
各SAORIモジュールは栞と同じ階層に置かれた定義ファイルに相対パスで指定された場所に置かれる。栞と同じ階層に置いて構わないが、作業ファイル等を使用する場合はdirectoryを掘ることが望ましい。
Proxyを経由するEXEモジュールの場合、Proxyの定義ファイル(proxy.ini)にProxyに対する相対パスを登録する。
定義ファイル
-SAORI.txt-
md5: md5.dll[CRLF]
proxy: proxy.dll[CRLF]
pinball: pinball\pinball.dll[CRLF]
-proxy.ini-
serch: serch.exe[CRLF]
update: update\update.exe[CRLF]
呼び出し例(擬似コード)
call(md5, string, sjesikl, lower);
call(pinball, start);
call(proxy, serch, google, 愛國戦隊);
call(proxy, update, all);
総括
類似案に拡張モジュール関係を一つのフォルダに纏めるという案も。
作業ファイル等の関係上、専用のフォルダを必要とする場合でも、柔軟に対応できる。 但し、ユーザ(ゴースト作者)が各設定ファイルを編集する必要がある。

試案2

home
 +-ghost
    +-[Name]
        +-ghost
           +-master
              +-shiori.dll
              +-SAORI
                 +-md5
                 |  +-md5.dll
                 |  +-descript.txt
                 +-serch
                 |  +-descript.txt
                 |  +-proxy.dll
                 |  +-proxy.ini
                 |  +-serch.exe
                 +-update
                 |  +-descript.txt
                 |  +-proxy.dll
                 |  +-proxy.ini
                 |  +-update.exe
                 |  +-tmp
                 |  |  +-tmp.dat
                 |  +-work.lst
                 +-pinball
                    +-descript.txt
                    +-pinball.dll
                    +-pinball.ini
各SAORIモジュールは栞のある階層のSAORIフォルダ以下に固有のフォルダを持ち、それぞれがdescript.txtを持つ。栞はload時にSAORI以下のフォルダを検索し、必要に応じて各モジュールをloadする。
定義ファイル
-descript.txt-
dll,md5.dll
load,dynamic
-proxy.ini-
update.exe
呼び出し例(擬似コード)
call(md5, string, sjesikl, lower);
call(pinball, start);
call(serch, google, 愛國戦隊);
call(update, all);
総括
いちいち固有のフォルダが必要になり、それぞれが定義ファイルを持つためやや煩雑。また、栞側でモジュールを検索してその情報を保持する必要がある。
但し、ユーザにしてみれば特定のフォルダ以下に配置するだけで使えるというのはメリットか。

試案3

home以下の特定のフォルダにまとめて配置し、ゴースト間で共有。
これは、SHIORI が固有のデータファイル等を持つ場合は、ここからカレントディレクトリを取得し、そこに自らのデータファイル一式を納めなくてはならない。という原則に反する上に、モジュールのバージョン不整合などの問題、さらにはSSPにおいては同時に同じモジュールが使用されかねないという問題をはらむ。

履歴

2002-01-30
名称仮決定。エラーの取り扱い。
2002-01-29
名称案。
ファイル配置。
2002-01-28-02
名称案。
返り値。
関数の形式。
ファイル配置。
2002-01-28-01
公開。

補足

この文書の内容は議論の叩き台です。


余談

各栞の固有スクリプトから呼び出されるのではなく、一定の条件下(Event, Reference[n]の内容等)で処理を全面的に代行する形式のバイナリ拡張フレームワークに関しても妄想中。もしくはキメラ栞(イベント処理は里々に任せて、AIトーク部分だけを別の栞で記述するとか)

誰もが最小限の労力で最大限の結果を得られるための手段として。
SDN(sdn@boreas.dti.ne.jp