これ の続き。ようやくVB.NET/C#.NETからPowerShellのスクリプト関数を定義して実行する目処がつきました。
内容は長いのでこちらに 。
2007/9/14
先日 の続き。未だSystem.Management.Automation.RunspaceにPowerShellスクリプト関数を定義して実行する方法がわからないので(2009/4/3 分かりました )、COM実装のMSScriptControl.ScriptControl使用に方針変更。
VB6ならば以下のようにすれば使えます。.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だけでやる方法もあります。
今週このネタ解決するだけで終わった orz
[参考]— posted by mu at 05:52 pm Comment [0] TrackBack [0]
2007/9/13
うわ、知らなかった。VBScriptで正規表現処理するVBScript.RegExpクラスなんてあったんだ。てっきり.NET Frameworkにしか無いと勘違いしてた。これ知ってれば、いくつかのVB6プログラムで仕様変更やあの手この手で代替処理しなくてもよかったのに orz
と恥をさらす記事でしたw
[参考]— posted by mu at 11:18 am Comment [0] TrackBack [0]
2007/9/10
先日 の続き。
流れは以下のようになるはずまずRunspaceConfigurationインスタンスに関数を登録する方法が不明。とりあえずこれは棚上げにして、引数なしでRunspaces.RunspaceConfiguration.Create()を呼び、インスタンスを生成。
次にPipeline.Commands.Add()メソッドは、cmdletまたはcmdletをパイプで繋いだものしか受け付けてくれないこと。つまりPowerShell上で一行づつ入力する形式そのまんまですね。といってもPowerShellで関数定義するようになんて入れても、次のPipeline.Invoke()の時点でエラーとなり、目論見が外れる。function add([double]$a,[double]$b){$a+$b}
となると関数はPipelineを作成する前に定義されてなければいけなくなる。それができるのは棚上げにしたRunspaceConfigurationインスタンス。このインスタンスを作成するときに.psc1ファイルを指定可能。これ拡張子が似てますがPowerShellのスクリプトファイルである.ps1とは別物で、試しに上の関数addを定義するだけの.ps1ファイルを指定したら、XML形式じゃないと怒られた。
.psc1ファイルを作成するひとつの方法は、PowerShellでexport-consoleを実行すること。試しに関数addを定義後、と打ち込むとこんなファイルができた。export-console -path .¥test
<?xml version="1.0" encoding="utf-8"?> <PSConsoleFile ConsoleSchemaVersion="1.0"> <PSVersion>1.0</PSVersion> <PSSnapIns /> </PSConsoleFile>うん、全く関数addの痕跡なしw このファイルを見る限り、どうやらSnapInのことしか記録してないっぽいです。
SnapInとは特殊な形式のDLLってことで正しいのでしょうか? Visual Studioでプロジェクト作って、PSSnapInを継承したクラスを作ってビルド、これでできたクラスはインストールすればcmdletとして扱われるみたいで、そうすればPipeline.Invoke()で実行できることでしょう。
しかし先日 にも書いたとおり、やりたいのは頻繁に変更される処理だけスクリプト化して柔軟性を増やすことなので、これでは本末転倒。今までスクリプト関数として処理していたものを、cmdletのパイプ繋ぎで実現(必要ならcmdletを作る)できるように考え方を変えないといけないのでしょうね。これ以上調べている時間もなさそうなので、今回もWindows Scripting Controlを使うことになりそうです。
[2009/3/2 追記] つづき
— posted by mu at 05:58 pm Comment [0] TrackBack [0]
2007/9/6
以前作ったプログラムで、GUIが必要なので基本はVB6で作り、頻繁に変更が発生しそうな計算だけMSScriptControl.ScriptControlクラスを使った物を作りました。あらかじめAddCodeメソッドで外部のテキストファイルに書かれているVBScriptの関数を登録、計算時にRunメソッドで関数を呼ぶというものです。計算部分だけがテキストファイルで分離されているので、そこの差し替えだけでその場で対応が取れたりと柔軟に機能。自分としては結構うまくいった解決でした。
今回基本部分がVB.NETに変わったので、.NET Frameworkを利用できるPowerShellを利用しようかと、昨日から下調べを始めました。
VB.NETからはSystem.Management.Automation空間のクラスを使うらしい。事前にPowerShellをインストールし、Vista対応のWindows SDKをインストールするか、C#と諸々 を参考にSystem.Management.Automation.dllを入手して、プロジェクトに参照登録する必要があります。
以下、こうすれば動くのではないかという想像。まだ試してません。さてPowerShellの使い方を覚えようと、XMLファイルを読むのを試しに作ってみる。以下のVB.NETのコードは動くことを確認。
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim xmldoc As New System.Xml.XmlDocument xmldoc.Load("sample.xml") Dim rootelement As System.Xml.XmlElement = xmldoc.DocumentElement TextBox1.Text = rootelement.Name End Sub End Classんで、これと同じことをPowerShellでやろうとコマンドを打ち込む。
PS C:¥XML> $xmldoc = new-object System.Xml.XmlDocument
PS C:¥XML> $xmldoc.Load("sample.xml")
PS C:¥XML> $rootelement = $xmldoc.DocumentElement
PS C:¥XML> $rootelement.Name
PS C:¥XML> $rootelement.GetType()
null 値の式ではメソッドを呼び出せません。
発生場所 行:1 文字:21
+ $rootelement.GetName( <<<< )
あら? $rootelement.Nameは何も表示しないし、GetType()を呼ぶと$rootelementはnullだという。う〜ん使い方が悪いのかXMLが読めていないのか、よくわからない。
以前JMP Version4 をCOM Automationで使おうとしたとき、あるメソッドがVB6からは難なくアクセスできるのに、VBScriptからだとエラーになるという問題がありました(今は修正されていますし、該当バージョンでも回避法があります)。これはVB6とVBScriptが似ていても中身は別物ということに起因するらしいのですが、なんとなくそのことが頭を離れない。PowerShellもそんなことやってたりするの?
[参考][2007/9/6 追記] どうもPowerShellからアクセスできるメソッドに制限があるらしい・・・
— posted by mu at 05:29 pm Comment [0] TrackBack [0]
Comments