ラベル .NETFramework の投稿を表示しています。 すべての投稿を表示
ラベル .NETFramework の投稿を表示しています。 すべての投稿を表示

2010年2月24日水曜日

PowerShellからWin32APIを使う

つい最近の話ですが、
PowerShellからiniファイルのデータを拾いたいと思う事がありました。
Win32APIのGetPrivateProfileString関数を
使えれば早いのですがそんなことが可能なのか。。。

実は、.NET FrameworkのP/Invoke(Platform Invokeの略?)の
仕組み機能を使用することで、PowerShellからWin32APIを使用できます。
例によって、.NET FrameworkをPowerShellから呼び出します。

今回のは、.NETにそんなに詳しくない身としては少々ハードルが高く、
一から実装することは難しいですので、
今回はこちらのサイトからソースコードを拝借しました。
ありがとうございます!

単にコードを示すだけでは私自身の勉強にならないため、
MSDNのクラスライブラリ等を使い、なるべくコードの意味を調べるようにしました。
関連リンクは後ほど。。。

サンプルコード:

# PowerShellからのPlatform APIの呼び出しは、
# .NET Frameworkの提供するP/Invoke機能を介して実施することができる。
# Invoke-Win32では、P/Invokeを実行用に一時的な型を定義し、
# そこに実装したStaticなメソッドを介してPlatformAPIを実行する。

function Invoke-Win32()
{
param
( [string]$dllName, [Type]$returnType, [string]$methodName, [Object[]]$parameterInfos )

$parameterTypes = $parameterInfos | %{ $_[ 0 ] }
$parameters = $parameterInfos | %{ $_[ 1 ] }

# カレントのアプリケーションドメインに、
# P/Invokeを実行するメソッドを持つ独自の型を定義する。

$domain = [AppDomain]::CurrentDomain
$name = New-Object Reflection.AssemblyName 'PInvokeAssembly'
$assembly = $domain.DefineDynamicAssembly( $name, 'Run' )
$module = $assembly.DefineDynamicModule( 'PInvokeModule')
$type = $module.DefineType( 'PInvokeType', "Public,BeforeFieldInit" )

# P/Invokeを呼び出すためのパラメーターを準備する。
# PlatformAPIに渡すパラメーターを保持する配列

$inputParameters = @()
# PSReference型パラメーターの位置を保持する配列
$refParameters = @()

for( $counter = 0; $counter -lt $parameterTypes.Length; $counter++ )
{
# パラメーターの型が「PSReference」のものについては、関数から戻ってきた値を拾えるようにする必要がある。
if( $parameterTypes[ $counter ] -eq [Ref] )
{
# 呼び出しの際に[out]をつける必要があるため、そのパラメーターの位置を退避しておく。
# 配列の数より1つ大きな数を保持しておく必要がある。(理由は後述)

$refParameters += $counter

# PSReference型を、.NETオブジェクトの参照型に書き換える。
$parameterTypes[ $counter ] = $parameters[ $counter ].Value.GetType().MakeByRefType()

# 関数呼び出し時に使用するパラメーター一覧に値を追加する
$inputParameters += $parameters[ $counter ].Value
}
# そうでないものについては、関数呼び出し時に使用するパラメーター一覧にただ追加するのみ
else
{
$inputParameters += $parameters[ $counter ]
}
}

# アセンブリーの動的メソッドとして、PlatformAPIを定義する
$method = $type.DefineMethod( $methodName, `
'Public,HideBySig,Static,PinvokeImpl', `
$returnType, `
$parameterTypes )
# PSReference型のパラメーターは、out属性のパラメーターとする。
foreach( $refParameter in $refParameters )
{
# 0番目の要素は戻り値の情報になるため、配列の要素番号+1を指定する必要がある。
$method.DefineParameter( ( $refParameter + 1 ), "Out", $null )
}

# P/Invokeのコンストラクターを設定する
$ctor = [Runtime.InteropServices.DllImportAttribute].GetConstructor( [string] )
$attr = New-Object Reflection.Emit.CustomAttributeBuilder $ctor, $dllName
$method.SetCustomAttribute( $attr )

# 一時的な型を作成し、そのメソッドとしてPlatformAPIを実行する。
$realType = $type.CreateType()
$returnObject = $realType.InvokeMember( $methodName, `
'Public,Static,InvokeMethod', `
$null, `
$null, `
$inputParameters )

# PSReference型で受け取ったパラメーターの値を更新する。
foreach( $refParameter in $refParameters )
{
$parameterInfos[ $refParameter ][ 1 ].Value = $inputParameters[ $refParameter ]
}
# PlatformAPIの戻り値を返す
return $returnObject
}

# GetPrivateProfileStringを呼び出してみる。
$iniFilePath = 'C:\test.ini'
$returnValue = New-Object System.Text.StringBuilder 500
$parameterInfos = @( @( [string], [string]"FrontOtherService1" ), `
@( [string], [string]"Name" ), `
@( [string], [string]"" ), `
@( [System.Text.StringBuilder], [System.Text.StringBuilder]$returnValue ), `
@( [int], [int]$returnValue.Capacity ), `
@( [string], [string]$file ) )

$returnValue = Invoke-Win32 -dllName "kernel32.dll" `
-returnType ( [UInt32] ) `
-methodName "GetPrivateProfileString" `
-parameterInfos $parameterInfos
$returnValue.ToString()



他にもいろいろなWin32API関数が使えるはずです。
その際、.NET上で動くコード(マネージコード)と
Win32APIを動かすときのコード(ネイティブコード)との対応を
理解しておく必要がありますが、ツールで調べることもできます
P/Invoke Interop Assistant)。

で、今新たに知りたいと思っているのは、
PowerShellからDelegateを使用する方法です。
イベントを使ったり、マルチスレッドを実装する場合には欠かせません。
これについても、おいおい勉強していければいいかなぁ。。。

参考コマンド:


参考リンク:
Precision Computing : Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters
MSDN : A Closer Look at Platform Invoke
P/Invoke.NET
CodePlex : P/Invoke Interop Assistant

関連投稿:
PowerShellから.NETのアセンブリを呼ぶ

修正履歴:

2010年2月14日日曜日

PowerShellにおける型オブジェクトの取得

以前のエントリー(このブログの最初のエントリーです)において、
関数を記述する際引数の型を縛る場合には、以下のような記述を行う必要がある、
ということを書きました。
例えば、パラメタを文字列型に固定したい場合には、
変数の前に[型名]という塊を添えました。

ここで、「[型名]」自体に何か意味はあるかと思い調べてみたところ、
こいつはSystem.Typeを継承したSystem.RunTimeという型のオブジェクトみたいです。
オブジェクトなので、そのメソッドなんかも利用できます。
また、この記述方法と-is演算子を用いれば、
オブジェクトの型チェックが簡単にできます。
Object -is [TypeName] というコードで型の判定ができるってのは、
コードが英語そのまんまですし、個人的にはかなりヒット(?)です。

検証スクリプト:

# 変数の型チェックに使用
$var_string = "defaultValue"
$var_string -is [string]
# 出力 → True


# [string]の型確認
Get-Member -InputObject ( [string] )
# 出力 → TypeName: System.RuntimeType(以下、メンバーなど)

# 他のクラスでやっても同様
Get-Member -InputObject ( [System.Runtime.InteropServices.DllImportAttribute] )
# 出力 → TypeName: System.RuntimeType(以下、メンバーなど)

# -is演算子の使用、クラスの継承関係は自動で考慮される
'sample_string' -is [string]
# 出力 → true
'sample_string' -is [object]
# 出力 → true
'sample_string' -is [uint32]
# 出力 → false

# 応用例:指定したディレクトリとそのサブディレクトリからファイルのみを取得
$TargetDirectory = 'D:\test'
$files = Get-ChildItem -Path $TargetDirectory -Recurse | ?{ $_ -is [IO.FileInfo] }$files | Get-Member
# 出力 → TypeName: System.IO.FileInfo(以下、メンバーなど)



PowerShellは触れば触るほど面白いなぁ、と思います。
シェルのセッション上のパイプを、.NET Frameworkのオブジェクトが動き回る、
という話についても、何か感覚的に分かってきた気がします。。。

参考サイト:

参考コマンド:

関連エントリー:
PowerShellのパラメータ属性に関するメモ

修正履歴:

2010年1月28日木曜日

PowerShellから.NETのアセンブリを呼ぶ

PowerShellを使っていると、
.NETのアセンブリを使いたくなる場面に頻繁に遭遇します。
PowerShellは.NET Framework上で動作するシェル言語ということで、
簡単にアクセスすることができます。

実行例:
.NETのアセンブリを使ってみる。

#System名前空間にあるメソッドには、[クラス名]::でアクセスできる
PS> $array = @("a","b","c")
PS> [String]::Join("|", $array)
#出力 → a|b|c

#System以外の名前空間にあるメソッドへは、[名前空間名.クラス名]::でアクセスできる。
PS> [System.IO.Path]::GetTempPath()
#出力 → TEMPフォルダのパス


#System以外の名前空間にあるメソッドへは、「System.」は省略可能。
#Systemがデフォルト、って事なんだろうか。。。
PS> [IO.Path]::GetTempPath()
#出力 → TEMPフォルダのパス

#System以外の名前空間にあるクラスのインスタンスを作る。
#メソッドの呼び出しと同様、「System.」は省略可能。
PS> $list = New-Object -TypeName "Collection.ArrayList"
PS> $list.GetType().Name
#出力 → ArrayList

#Systemの付かない名前空間にアクセスする場合、アセンブリを予めロードする必要がある。
PS> [Microsoft.VisualBasic.Constants]::vbTab
#エラーパイプライン →
[Microsoft.VisualBasic.Constants]が見つかりません。この型を含むアセンブリが読み込まれていることを確認してください。

PS> [Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic") | Out-Null
PS> [Microsoft.VisualBasic.Constants]::vbAbortRetryIgnore
#出力 → DefaultButton1, OkOnly, AbortRetryIgnore



参考コマンド:

参考リンク:

関連投稿:
PowerShellからWin32APIを使う

修正履歴:
2010/02/02:実装例のソースコードを若干修正。
2010/02/24:関連投稿のリンクを追加