UWSCのpowershell関数をスピードアップする(その2)

前回は、IE経由でした。
UWSCのpowershell関数をスピードアップする - じゅんじゅんのきまぐれ
あれはあれで、オブジェクトを渡せる可能性を秘めているわけですが、IEを起動したりして、起動が遅い。
UWSC公式掲示板で、pshモジュールを利用した回答を書いた時、起動の遅さにうんざりしました。
ということで、名前付きパイプを使って、早くしてみました!



スクリプト

ちなみに、一番苦戦したのは、Powershellでブロックしない名前付きパイプの作成。
ブロックすると、いってしまうのです、UWSCが。
psh.uws

OPTION EXPLICIT

IFB GET_UWSC_NAME = "psh.uws" THEN
	psh.Create()

	DIM code = ""
	WHILE code <> EMPTY
		code = psh.Run(code)
		IF LENGTH(code) THEN PRINT code
		code = INPUT("?")
	WEND

	psh.Dispose()
ENDIF


MODULE psh
	CONST _pipe_name = "JuneUwscPsh"
	CONST _pipe_full_name = "\\.\pipe\" + _pipe_name
	CONST _size = 1024
	DIM _hPipe

	DEF_DLL CloseHandle(dword): bool: kernel32.dll
	CONST INVALID_HANDLE_VALUE = $FFFFFFFF
	DEF_DLL WaitNamedPipeW(wstring,dword): bool: kernel32.dll
	DEF_DLL CreateFileW(wstring,dword,dword,dword,dword,dword,dword): dword: kernel32.dll
	CONST GENERIC_ALL = $10000000
	CONST OPEN_EXISTING = 3
	DEF_DLL ReadFile(dword,var byte[],dword,var dword,dword): bool: kernel32.dll
	DEF_DLL WriteFile(dword,byte[],dword,var dword,dword): bool: kernel32.dll

	TEXTBLOCK _psh_code
		if (-not ("CallbackEventBridge" -as [type])) {
			Add-Type @"
				using System;
				public sealed class CallbackEventBridge
				{
					public event AsyncCallback CallbackComplete = delegate {};
					private void CallbackInternal(IAsyncResult result)
					{
						CallbackComplete(result);
					}
					public AsyncCallback Callback
					{
						get { return new AsyncCallback(CallbackInternal); }
					}
				}
"@}
		$po = New-Object PSObject
		$po | Add-Member NoteProperty "buf" (New-Object byte[] 1024)
		$po | Add-Member NoteProperty "pipe" (New-Object System.IO.Pipes.NamedPipeServerStream "$$name$$", InOut, 1, Message, Asynchronous, 1024, 1024)
		$po | Add-Member NoteProperty "wb" (New-Object CallbackEventBridge)
		$po | Add-Member NoteProperty "rb" (New-Object CallbackEventBridge)
		$callback = {
			param($asyncResult)
			# 本当は書き込んだサイズをチェックすべきかも
			$asyncResult.AsyncState.EndWrite($asyncResult)
		}
		Register-ObjectEvent -InputObject $po.wb -EventName CallbackComplete -action $callback > $null
		$callback = {
			param($asyncResult)
			$asyncResult.AsyncState.pipe.EndWaitForConnection($asyncResult)
		}
		Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null
		$ar = $po.pipe.BeginWaitForConnection($po.rb.Callback, $po)
		while(!$ar.IsCompleted) {
			Start-Sleep -Milliseconds 100
		}
		
		$callback = {
			param($asyncResult)
			$po = $asyncResult.AsyncState
			$len = $po.pipe.EndRead($asyncResult)
			$cmd = [System.Text.Encoding]::Unicode.GetString($po.buf, 0, $len)
			if($cmd -match '^exit|^quit') {
				$po.pipe.Close()
			} else {
				try {
					$cmd = "" + (iex $cmd)
				} catch {
					$cmd = "Invoke error. " + $cmd + "`n" + ($Error[0] | Out-String)
				}
				$cb = [System.Text.Encoding]::Unicode.GetBytes($cmd)
				$wb = New-Object byte[] ($cb.Length + 4)
				[Array]::Copy([BitConverter]::GetBytes($cb.Length), 0, $wb, 0, 4)
				[Array]::Copy($cb, 0, $wb, 4, $cb.Length)
				$ar = $po.pipe.BeginWrite($wb, 0, $wb.Length, $po.wb.Callback, $po)
				while(!$ar.IsCompleted -and $po.pipe.IsConnected) {
					Start-Sleep -Milliseconds 100
				}
			}
		}
		Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null
		while($po.pipe.IsConnected) {
			$ar = $po.pipe.BeginRead($po.buf, 0, $po.buf.Length, $po.rb.Callback, $po)
			while(!$ar.IsCompleted) {
				Start-Sleep -Milliseconds 100
			}
		}
		$po.pipe.Close()
	ENDTEXTBLOCK

	FUNCTION Create()
		POWERSHELL(REPLACE(_psh_code, "$$name$$", _pipe_name), TRUE)
		DIM count = 0, wait = 30
		WHILE !WaitNamedPipeW(_pipe_full_name, 0) AND count < wait
			SLEEP(0.1)
			count = count + 1
		WEND
		RESULT = FALSE
		IFB count < wait THEN
			_hPipe = CreateFileW(_pipe_full_name, GENERIC_ALL, 0, NULL, OPEN_EXISTING, 0, NULL)
			IFB _hPipe = INVALID_HANDLE_VALUE THEN
				PRINT "Err: CreateFile."
			ELSE
				RESULT = TRUE
			ENDIF
		ELSE
			PRINT "Err: WaitNamedPipe."
		ENDIF
	FEND

	PROCEDURE Dispose()
		WriteString("exit")
		IFB _hPipe <> 0 AND !CloseHandle(_hPipe) THEN
			PRINT "Err: CloseHandle."
		ENDIF
	FEND

	FUNCTION Run(code)
		RESULT = EMPTY
		IF WriteString(code) THEN RESULT = ReadString()
	FEND

	FUNCTION ReadString()
		DIM buf[_size], ret, i, rest = 0
		RESULT = ReadFile(_hPipe, buf, 4, ret, NULL) AND ret = 4
		IFB RESULT THEN
			rest = ((buf[3] * 256 + buf[2]) * 256 + buf[1]) * 256 + buf[0]
			RESULT = ""
		ELSE
			PRINT "Err: ReadFile size."
		ENDIF
		WHILE rest > 0
			IFB ReadFile(_hPipe, buf, _size, ret, NULL) THEN
				FOR i = 0 TO INT(ret / 2) - 1
					RESULT = RESULT + CHR(buf[i*2] + buf[i*2+1] * 256)
				NEXT
				rest = rest - ret
			ELSE
				PRINT "Err: ReadFile."
				BREAK
			ENDIF
		WEND
	FEND

	FUNCTION WriteString(data)
		DIM len = LENGTH(data) * 2, buf[len], i, t
		RESULT = len
		IFB RESULT THEN
			FOR i = 0 TO len / 2 - 1
				t = ASC(COPY(data, i + 1, 1))
				buf[i * 2] = t MOD 256
				buf[i * 2 + 1] = INT(t / 256)
			NEXT
			// 本来なら少量書込みでの続行処理が必要
			RESULT = (WriteFile(_hPipe, buf, len, t, NULL) > 0 AND t = len)
			IF !RESULT THEN PRINT "Err: WriteFile."
		ENDIF
	FEND

ENDMODULE

使い方は前回と同じ。
本モジュールをCALLする。
最初に「psh.Create()」
最後に「psh.Dispose()」
今までPowershell関数を呼んでいたところを、psh.Run関数に置き換える。
psh.Disposeしないと不幸になるのは、前回と同じ。
前回は野良Powershellと野良IEができてましたが、今回は野良Powershellだけで済みますけど。


これさー、成果はすんなり動くけど、ここまでくるの大変なんだぜ。
Powershell内もUWSC側もAPI等でブロックしたら、アウト。
そこに注意しながら、いろんなところにWhileループが入っているのです。


ちなみに、UWSC->Powershellのコマンドは、500文字程度までの想定。
戻りは何文字でも良いように、最初にサイズを送るようにしている。
コマンドももっとたくさんにしたい場合は、最初にサイズを送るようにすべき。