スクリプトプラグインの実装方法

---
### StreamRelay.NET.exe の スクリプト実行機能を呼び出しているプラグインの実装方法について説明する

StreamRelay.NET.exe/StreamRelay.NET.x86.exeには、いくつかの機能をプラグインとして実装できるようにインターフェイスを公開している。

今回は、スクリプトを実行するためのインターフェイスを使って、プラグインを作成する方法を記述する。

---
### 既存のプラグイン

JScript.NETやLuaなどのスクリプトの呼び出し箇所が、このプラグインで、実装されている。

プラグイン的には、このあたり。

- Plugins\JScript.NET.dll
- Plugins\Plugin.Boo.dll
- Plugins\Plugin.IronPython.dll
- Plugins\Plugin.IronRuby.dll
- Plugins\Plugin.Lua.dll
- Plugins\ScriptPluginSample.dl_

これ以外にも、正規表現処理を行う部分(StreamRelay.NET.exe/StreamRelay.NET.x86.exeに直接実装)も、このプラグインインターフェイス経由で呼び出している。

---
### 実装したプラグインの呼び出され方

StreamRelay.NET.exe/StreamRelay.NET.x86.exeは、Streamクラスのread() でデータを読み取り、スクリプトプラグインのインターフェイスのコードが実行されて、write()で書き出される。

文字コード変換系のオプションが指定されていたら、
Readerクラスのread() でデータを文字として読み取り、スクリプトプラグインのインターフェイスのコードが実行されて、Writerクラスで書き出される。

という感じ。

![ScriptPlugin02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/3651b303-6b40-4efe-6c7f-8fa7d767c087.png)

---
### 実装するインターフェイス

以下の2つのインターフェイスを継承したクラスを実装すればよい

- IPlugin.dll 中の jp.dip.rocketeer.Plugins.IPluginScriptForStreamRelay インターフェイス
- IPlugin.dll 中の jp.dip.rocketeer.Plugins.IPluginScriptEntityForStreamRelay インターフェイス

---
### jp.dip.rocketeer.Plugins.IPluginScriptForStreamRelay インターフェイス

StreamRelay.NET.exeが、引数/オプションの解析処理中に呼び出してくれる。

具体的には、起動直後の引数/オプションの解析処理中に、
**int Args(String iStr1, String iStr2, String iStr3, ref String errorStr, ref IPluginScriptEntityBase iObj);**
を呼び出すので、自分の引数であれば、それに基づいてIPluginScriptEntityForStreamRelayを継承したオブジェクトを作って、第五引数に充てて返す。そして利用したオプションの数を返す。
自分の引数でなければ、0を返す。


より、具体的には、
例えば、
`c:\>StreamRelay.NET.exe a b c d e f`
というコマンドラインで起動したら、

1. iStr1=a, iStr2=b,　iStr3=c, で、Args() を呼び出し、
2. iStr1=b, iStr2=c,　iStr3=d, で、Args() を呼び出し、
3. iStr1=c, iStr2=d,　iStr3=e, で、Args() を呼び出し、
4. iStr1=d, iStr2=e,　iStr3=f, で、Args() を呼び出し、
5. iStr1=e, iStr2=f,　iStr3=NULL, で、Args() を呼び出し、

という感じ・・・

例えば、iStr1=b, iStr2=c,　iStr3=d, の時、自分の引数ということで、iStr1=b, iStr2=c, を Args() で使用したら、2を返す。すると次は iStr1=d, iStr2=e,　iStr3=f, で、Args() が呼び出される。

ということになっている。

そして、最後にパラメータが必要のないプラグイン用に「**String Args(ref IPluginScriptEntityBase iObj)**」が呼ばれる

---
#### jp.dip.rocketeer.Plugins.IPluginScriptForStreamRelay インターフェイスで定義しているメソッド/プロパティ

実装する必要があるのは以下

jp.dip.rocketeer.Plugins.IPluginScriptMainBaseインターフェイスで定義されているのは、以下のとおり

<dl>
 <dt><b>int Args(String iStr1, String iStr2, String iStr3, ref String err, ref IPluginScriptEntityBase iObj);</b>
  <dd>引数を解析して、自分のものだったかどうかを返す(私は「名前」「値(スクリプトファイル名)」「文字コード」の三つを必要とするバージョンだけど、お好きな引数(最大3つ)を渡すバージョン)
 <dt><b>int Args(String iStr1, String iStr2, Encoding iStr3, ref String err, ref IPluginScriptEntityBase iObj);</b>
  <dd>引数を解析して、自分のものだったかどうかを返す(sWebTool側から呼ばれるので、StreamRelay.NET.exe専用のプラグインなら、適当に実装しておいてくれればよい)
 <dt><b>String Args(ref IPluginScriptEntityBase iObj);</b>
  <dd>引数なしでロードするスクリプト(というかDLLバイナリ型のスクリプト(プラグインプログラム))なら(引数は必要ないのであれば)、こちらを使う
</dl>

jp.dip.rocketeer.Plugins.IPluginScriptBaseインターフェイスで定義されているのは、以下

<dl>
 <dt><b>Dictionary<String, String> usage { get; }</b>
  <dd>ヘルプに引数を表示する。(-helpなどの時に、「文字列」「文字列」(例えば「-arg1 arg2」な感じ)という感じでヘルプ表示(usage)するので、それを与えておいてほしい)
 <dt><b>String ScriptLanguageName { get; }</b>
  <dd>スクリプト言語を表示する(スクリプトを実行するプラグインなので・・・)
 <dt><b>Boolean NeedParam { get; }</b>
  <dd>パラメータ(ファイルパス)が必要なモジュールかどうかを示す(通常はtrueだと思うけど、これをfalseにすると(引数なしで常にロードするようなプラグインということ)、引数解析が終わった後に Args(ref IPluginScriptEntityBase iObj);が呼ばれる)
 <dt><b>PluginManagePublicObj PluginManagePublicObj { get; set; }</b>
  <dd>各種プラグインへのインターフェイスの公開部分(これは変数を宣言しておくだけでよい(IPlugin.dll側でよきに計らっている))
</dl>

jp.dip.rocketeer.Plugins.IPluginBaseインターフェイスで定義されている分については、[インターフェイス IPluginBase について](https://qiita.com/tomoki0sanaki/items/60613c29f0d627353e67)を参照

---
### jp.dip.rocketeer.Plugins.IPluginScriptEntityForStreamRelay インターフェイス

これは、Stream(Reader/Writer)のread()後に、このインターフェイスを継承したオブジェクトの **GetExecuteResult(IPluginScriptBeanForStreamRelay inputdataObj)** が呼び出される。
戻り値は、エラー文字列なので、通常は空文字列かNULLを返せばよい。

---
#### jp.dip.rocketeer.Plugins.IPluginScriptEntityForStreamRelay インターフェイスで定義しているメソッド/プロパティ

実装する必要があるのは以下

jp.dip.rocketeer.Plugins.IPluginScriptEntityForStreamRelayインターフェイスで定義されているString GetExecuteResult(IPluginScriptBeanForStreamRelay inputdataObj);は、別途説明しているので省略


jp.dip.rocketeer.Plugins.IPluginScriptEntityBaseインターフェイスで定義されているのは、以下

<dl>
 <dt><b>String ScriptLanguageName { get; }</b>
  <dd>スクリプト言語を表示する
 <dt><b>String ScriptConfig { get; }</b>
  <dd>現在の状態を表示する(設定を受け取っての状態(Verboseモード時や接続状態の表示時に呼び出される))(入力側)
 <dt><b>String ScriptName { get; }</b>
  <dd>スクリプト言語など、簡単な情報を表示する
 <dt><b>String ScriptFileName { get; }</b>
  <dd>スクリプトファイルパスを表示する
 <dt><b>Encoding ScriptEncoding { get; set; }</b>
  <dd>スクリプトファイルの文字コード(NULLの場合は、既定の文字コードとなる)
 <dt><b>String ScriptEncodingName { get; }</b>
  <dd>スクリプトファイルの文字コード(NULLや空文字列の場合は、既定の文字コードとなる)
 <dt><b>TagObject LocalTag { get; set; }</b>
  <dd>何度も実行されるスクリプト間で保持するデータを保持するオブジェクト(これは変数を宣言しておくだけでよい(IPlugin.dll側でよきに計らっている))
 <dt><b>Boolean CanExecute { get; }</b>
  <dd>スクリプト実行が可能かどうか(コンパイルに失敗したとか・・・)
 <dt><b>PluginManagePublicObj PluginManagePublicObj { get; set; }</b>
  <dd>各種プラグインへのインターフェイスの公開部分(これは変数を宣言しておくだけでよい(IPlugin.dll側でよきに計らっている))
 <dt><b>String CreateScriptObj();</b>
  <dd>スクリプトファイルをロードする(通常は自プロパティのスクリプトファイルを使って下記のCreateScriptObjFromFile(this.ScriptFileName)を呼ぶことになると思う)
 <dt><b>String CreateScriptObjFromFile(String ScriptFile);</b>
  <dd>スクリプトファイルをロードする(通常はファイルを読み込んでCreateScriptObj(String ScriptCode)を呼ぶことになると思う)
 <dt><b>String CreateScriptObj(String ScriptCode);</b>
  <dd>スクリプトファイルをロードする
</dl>

jp.dip.rocketeer.Plugins.IPluginBaseインターフェイスで定義されている分については、[インターフェイス IPluginBase について](https://qiita.com/tomoki0sanaki/items/60613c29f0d627353e67)を参照

---
### IPluginScriptBeanForStreamRelay オブジェクト

GetExecuteResult() の引数**IPluginScriptBeanForStreamRelayオブジェクト**の、メソッド/プロパティは、以下のような感じ。

これらのメソッド/プロパティを操作してくれればよい。

<dl>
 <dt><b>String InputStr { get; set; }</b>
  <dd>文字コード指定の場合、ストリームデータを文字列に置換した状態でこれに格納されてる。また、これに格納しておくことで、ストリームへ出力される
 <dt><b>byte[] InputByteArray { get; set; }</b>
  <dd>文字コードを指定していない場合、ストリームデータをバイト列としてこれに格納されている。また、これに格納しておくことで、ストリームへ出力される
 <dt><b>String BufferStr { get; set; }</b>
  <dd>一時格納用、次の呼び出しでも保持される。TCPの接続とストリームの方向ごとに独立で保持される
 <dt><b>byte[] BufferByteArray { get; set; }</b>
  <dd>一時格納用、次の呼び出しでも保持される。TCPの接続とストリームの方向ごとに独立で保持される
 <dt><b>Boolean IsSend { get; set; }</b>
  <dd>このスクリプト呼び出し後に InputByteArray/InputStr のデータを転送したい場合 true をセットする(既定はtrue)
 <dt><b>String InputStrReverse { get; set; }</b>
  <dd>返信したい場合、こちらに格納する。文字コードを指定した場合は String 型の変数へ、そうでない場合は、byte配列型の変数へ格納すること
 <dt><b>byte[] InputByteArrayReverse { get; set; }</b>
  <dd>返信したい場合、こちらに格納する。文字コードを指定した場合は String 型の変数へ、そうでない場合は、byte配列型の変数へ格納すること
 <dt><b>Boolean IsSendReverse { get; set; }</b>
  <dd>このスクリプト呼び出し後に InputByteArrayReverse/InputStrReverse のデータを返信したい場合 true をセットする(既定はfalse)
 <dt><b>Boolean IsClose { get; set; }</b>
  <dd>このスクリプト呼び出し後に、コネクションを切断したい場合 true をセットする(既定はfalse)
 <dt><b>Boolean IsRequest { get; }</b>
  <dd>このスクリプトが「リクエスト」のストリームで呼び出された場合 true。「レスポンス」のストリームで呼び出された場合 false
 <dt><b>void logWrite(String iStr);</b>
  <dd>ログを書き出す(ロギング機能自体もプラグイン化している)
 <dt><b>Boolean IsInput { get; }</b>
  <dd>このスクリプトが入力側で呼び出された場合 true。出力側で呼び出された場合 false<br><br>
 <dt><b>int count { get; }</b>
  <dd>呼び出された回数。(「count=0」は接続直後なので、データ(InputByteArray/InputStr)は空のはず)
 <dt><b>String InputCallerAddress { get; }</b>
  <dd>入力側の相手のアドレス、またはプログラム、ファイル、標準入力
 <dt><b>int InputCallerPort { get; }</b>
  <dd>入力側の相手のポート番号
 <dt><b>String InputMyselfAddress { get; }</b>
  <dd>入力側の自分自身のアドレス、またはプログラム、ファイル、標準入力
 <dt><b>int InputMyselfPort { get; }</b>
  <dd>入力側の自分自身のポート番号
 <dt><b>String OutputCallerAddress { get; }</b>
  <dd>出力側の相手のアドレス、またはプログラム、ファイル、標準入力
 <dt><b>int OutputCallerPort { get; }</b>
  <dd>出力側の相手のポート番号
 <dt><b>String OutputMyselfAddress { get; }</b>
  <dd>出力側の自分自身のアドレス、またはプログラム、ファイル、標準入力
 <dt><b>int OutputMyselfPort { get; }</b>
  <dd>出力側の自分自身のポート番号
</dl>

---
### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(バイト配列/文字配列)

「-LocalCharset」など、文字コードが指定されていれば、文字配列が与えられる。

つまり、
IPluginScriptBeanForStreamRelay#**InputByteArray** = NULL
で
IPluginScriptBeanForStreamRelay#**InputStr** にデータが入っている。

そうでない場合は、バイト配列のデータが与えられている。

つまり、
IPluginScriptBeanForStreamRelay#**InputStr** = ""
で
IPluginScriptBeanForStreamRelay#**InputByteArray** にデータが入っている。

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(データを転送したい)

データを転送する場合は、

IPluginScriptBeanForStreamRelay#**InputByteArray**
または
IPluginScriptBeanForStreamRelay#**InputStr**
にデータをセットして、
IPluginScriptBeanForStreamRelay#**IsSend = TRUE**
にセットする

![ScriptPlugin04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/e9e1e65b-ef61-a462-9cff-733aa8fb98ba.png)

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(データを返送したい)

データを返送する場合(例えば、Localと呼称されるストリームから受信しているのだが、そのままLocalと呼称されるストリームへ返事したい)は、

IPluginScriptBeanForStreamRelay#**InputByteArray**
または
IPluginScriptBeanForStreamRelay#**InputByteArrayReverse**
にデータをセットして、
IPluginScriptBeanForStreamRelay#**IsSendReverse = TRUE**
にセットする

![ScriptPlugin03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/018f17a4-30b3-5a5b-4faa-df0ad6d2fcba.png)

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(呼び出されるタイミング)

呼び出されるタイミングは、Stream/Readerのread()のタイミングなので、大体バッファサイズ単位だと思う。
オプション「**-BufferSize**」(既定値は**2048**)あたりがそのサイズ。

行単位で処理したいなどの場合は、後述のバッファリングする処理が必要かと思う。

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(バッファリング)

呼び出しタイミングを超えたデータをまとめたいとかの時には、以下のバッファリング用の変数が使えると思う。

IPluginScriptBeanForStreamRelay#**BufferStr**
と
IPluginScriptBeanForStreamRelay#**BufferByteArray**

例えば、今回の呼び出しは全てバッファリングしたので、転送するデータがないということなれば、

IPluginScriptBeanForStreamRelay#**IsSend = FALSE**
にセットして、Writeしないようにしておくこと。

または、
IPluginScriptBeanForStreamRelay#**InputByteArray**
と
IPluginScriptBeanForStreamRelay#**InputStr**
を空文字列/NULLにしてもいいけど、**IsSendプロパティをFALSEにセット**した方がよいと思う。

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(切断したい)

IPluginScriptBeanForStreamRelay#**IsClose = TRUE**
にセットすると、GetExecuteResult()直後にその接続は切断される。

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(呼び出し回数)

まず一番最初は、IPluginScriptBeanForStreamRelay#init() が呼ばれるのだが、

Streamを生成した時点で、
IPluginScriptBeanForStreamRelay#**count = 0** で
IPluginScriptBeanForStreamRelay#**InputStr = ""**
IPluginScriptBeanForStreamRelay#**InputByteArray = NULL**
が呼び出される。
(データを読み出す前、接続直後などに何かしたければ、このタイミングでスクリプトを書いてくれればよい)

次の最初のread()の時に、
IPluginScriptBeanForStreamRelay#**count = 1** で
IPluginScriptBeanForStreamRelay#**InputStr** と
IPluginScriptBeanForStreamRelay#**InputByteArray** のどちらかにデータがある状態で呼び出される。

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(最後の呼び出し)

IPluginScriptBeanForStreamRelay#**IsClose = TRUE** で呼びだされる。

切断時に何か処理する必要があるなら(バッファリングしたデータを全て吐き出すとか)、このタイミングのスクリプトを書いてほしい。

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(接続元/接続先)

入力側の接続先相手のアドレスとポート
・IPluginScriptBeanForStreamRelay#**InputCallerAddress**
・IPluginScriptBeanForStreamRelay#**InputCallerPort**

入力側の自分のアドレスとポート
・IPluginScriptBeanForStreamRelay#**InputMyselfAddress**
・IPluginScriptBeanForStreamRelay#**InputMyselfPort**

出力側の接続先相手のアドレスとポート
・IPluginScriptBeanForStreamRelay#**OutputCallerAddress**
・IPluginScriptBeanForStreamRelay#**OutputCallerPort**

出力側の自分のアドレスとポート
・IPluginScriptBeanForStreamRelay#**OutputMyselfAddress**
・IPluginScriptBeanForStreamRelay#**OutputMyselfPort**

アクセス制限や何か処理を換えたいなら、このあたりが使えるかと思う。

![ScriptPlugin01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/f9df383b-7f02-127c-519a-a0ba68d2fb6b.png)

---
#### IPluginScriptBeanForStreamRelay オブジェクトの使い方の方針(通信の方向)

通信の方向(リクエストなのか、レスポンスなのか)は、以下のプロパティで判断できると思う。

IPluginScriptBeanForStreamRelay#**IsRequest**


---
### 目次へ戻る

[目次というか最初の一歩](http://qiita.com/tomoki0sanaki/items/85f20cbe18378e33cd6f)