先日 の続き。未だSystem.Management.Automation.RunspaceにPowerShellスクリプト関数を定義して実行する方法がわからないので(2009/4/3 分かりました )、COM実装のMSScriptControl.ScriptControl使用に方針変更。
VB6ならば以下のようにすれば使えます。- Microsoft Script Control 1.0を参照設定
- MSScriptControl.ScriptControlクラスのインスタンスを作成
- Languageプロパティに言語を指定(今回は"VBScript")
- AddCodeメソッドで関数を定義する
- Runメソッドで定義した関数を呼ぶ
.NET上で作成したCOMインスタンスは開放処理しないとメモリに残り続ける という手間はあるようですが、.NET上でもCOMは.NET Frameworkとほぼ等価に扱えるため、同じ手法で実行できるはず。
が、トラブる。Runメソッドを呼ぶ段階になるとなる例外を出してしまいます。原因はRunメソッドが第2引数以降を可変長引数(VBでいうParamArray)を取るため。これの扱いはSystem.ArgumentException: HRESULT からの例外: 0x800A01C2 場所 MSScriptControl.ScriptControlClass.Run(String ProcedureName, Object[]& Parameters)
環境 | 可変長引数の渡し方 | 型 |
---|---|---|
Visual Studio 6/COM | Variant配列への参照 | Object[]& |
Visual Stduio .NET/.NET Framework | Object配列の実値 | Object[] |
呼ばれる側のMSScriptControlはバイナリでいじれないので、呼ぶ側がObject[]&型で引数を用意して渡す必要があります。C#ではrefを前に付ければObject[]&型を渡せる ようですが・・・VB.NETでどうやるんだろ、これ?
調べてもVB.NETで実現方法がわからなかったので、C#で以下のようなMSScriptControlを扱うクラスを作って対処することになりました。う〜ん、[oneway]のためだけにC#が必要 とか、凝った事するとなるとC#なんでしょうか・・・using System; using System.Collections.Generic; using System.Text; namespace MSScriptControlInterface { public class ScriptControlInterface { protected MSScriptControl.ScriptControl mScriptControlInstance; public ScriptControlInterface() { // // TODO: Add constructor logic here // mScriptControlInstance = new MSScriptControl.ScriptControl(); mScriptControlInstance.Language = "VBScript"; } ~ ScriptControlInterface() { System.Runtime.InteropServices.Marshal.ReleaseComObject(mScriptControlInstance); } public void AddCode(String code) { mScriptControlInstance.AddCode(code); } public object Run(string name, object[] arguments) { return mScriptControlInstance.Run(name, ref arguments); } } }VB.NETだけでやる方法もあります。
- AddCode()で定義する関数は
- 引数を取らないようにする
- 次のステップで渡すグローバル変数から引数を受け取る
- 渡したい値はAddObject()でグローバル変数として入れておく
- Eval()で関数を呼び、結果を受け取る (Eval("f()")など)
今週このネタ解決するだけで終わった orz
[参考]
Comments