UWSCで名前付きパイプ

UWSCでプロセス間通信をやってみようと思い立ったので、やってみた。
メモリマップドファイル方式は、しろまささんのところにあるので、
http://siromasa.xxxxxxxx.jp/Sample/index.html
名前付きパイプにて。
これなら、別端末でも通信できますね。



まずはスクリプト

pipe.uws

OPTION EXPLICIT

IFB GET_UWSC_NAME = "pipe.uws" THEN
	DIM ret, loop = TRUE
	IFB MSGBOX("Server?", BTN_YES or BTN_NO) = BTN_YES THEN
		ret = Pipe.Create()
		loop = (ret = 0)
		WHILE loop
			ret = Pipe.Wait()

			// INPUTかつThreadがAPI内でBlockすると、止まる模様。とりあえず一方向
			ret = ""
			WHILE VARTYPE(ret) = VAR_BSTR
				Pipe.Log(ret)
				ret = Pipe.ReadString()
			WEND

			Pipe.Disconnect()
			loop = (MSGBOX("Wait?", BTN_YES or BTN_NO) = BTN_YES)
		WEND
		Pipe.Close()
	ELSE
		WHILE loop
			ret = Pipe.Connect()

			IFB ret = 0 THEN
				// INPUTかつThreadがAPI内でBlockすると、止まる模様。とりあえず一方向
				ret = INPUT("Message to server")
				WHILE ret <> EMPTY
					Pipe.WriteString(ret, FALSE)
					ret = INPUT("Message to server")
				WEND

				Pipe.Close(FALSE)
			ENDIF
			loop = (MSGBOX("Connect?", BTN_YES or BTN_NO) = BTN_YES)
		WEND
	ENDIF
ENDIF



MODULE Pipe
	CONST DEF_NAME = "\\.\pipe\UwscNamedPipe"
	CONST DEF_TIMEOUT = 1000
	HASHTBL _handleServer
	HASHTBL _handleClient
	DIM _log = TRUE

	// General API
	DEF_DLL CloseHandle(dword): bool: kernel32.dll
	DEF_DLL GetLastError(): dword: kernel32.dll

	// NamedPipe API
	DEF_DLL CreateNamedPipeW(wstring,dword,dword,dword,dword,dword,dword,dword): dword: kernel32.dll
	CONST PIPE_ACCESS_INBOUND = 1
	CONST PIPE_ACCESS_OUTBOUND = 2
	CONST PIPE_ACCESS_DUPLEX = 3
	CONST PIPE_UNLIMITED_INSTANCES = 255
	CONST INVALID_HANDLE_VALUE = $FFFFFFFF
	DEF_DLL ConnectNamedPipe(dword,dword): bool: kernel32.dll
	DEF_DLL DisconnectNamedPipe(dword): bool: kernel32.dll
	DEF_DLL PeekNamedPipe(dowrd,var byte[],dword,var dword,var dword,var dword): bool: kernel32.dll
	DEF_DLL TransactNamedPipe(dword,byte[],dword,var byte[],dword,var dword,dword): bool: kernel32.dll
	DEF_DLL WaitNamedPipeW(wstring,dword): bool: kernel32.dll

	// File API
	DEF_DLL CreateFileW(wstring,dword,dword,dword,dword,dword,dword): dword: kernel32.dll
	CONST GENERIC_READ = $80000000
	CONST GENERIC_WRITE = $40000000
	CONST GENERIC_EXECUTE = $20000000
	CONST GENERIC_ALL = $10000000
	CONST CREATE_NEW = 1
	CONST CREATE_ALWAYS = 2
	CONST OPEN_EXISTING = 3
	CONST OPEN_ALWAYS = 4
	CONST TRUNCATE_EXISTING = 5
	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


	FUNCTION Create(name = DEF_NAME, timeout = DEF_TIMEOUT, dir = PIPE_ACCESS_DUPLEX, num = PIPE_UNLIMITED_INSTANCES)
		RESULT = 0
		_handleServer[name] = CreateNamedPipeW(name, dir, 0, num, 0, 0, timeout, NULL)
		IFB _handleServer[name] = INVALID_HANDLE_VALUE THEN
			RESULT = GetLastError()
			Log("Err: CreateNamedPipe: " + RESULT)
		ENDIF
	FEND

	FUNCTION Wait(name = DEF_NAME)
		RESULT = 0
		IFB !ConnectNamedPipe(_handleServer[name], NULL) THEN
			RESULT = GetLastError()
			Log("Err: ConnectNamedPipe: " + RESULT)
		ENDIF
	FEND

	FUNCTION Disconnect(name = DEF_NAME)
		RESULT = 0
		IFB !DisconnectNamedPipe(_handleServer[name]) THEN
			RESULT = GetLastError()
			IF RESULT THEN Log("Err: DisconnectNamedPipe: " + RESULT)
		ENDIF
	FEND

	FUNCTION Close(server = TRUE, name = DEF_NAME)
		RESULT = 0
		DIM handle
		IFB server THEN
			handle = _handleServer[name]
		ELSE
			handle = _handleClient[name]
		ENDIF
		IFB !CloseHandle(handle) THEN
			RESULT = GetLastError()
			Log("Err: CloseHandle: " + RESULT)
		ENDIF
	FEND

	FUNCTION Connect(name = DEF_NAME)
		RESULT = 0
		IFB WaitNamedPipeW(name, 0) THEN
			_handleClient[name] = CreateFileW(name, GENERIC_ALL, 0, NULL, OPEN_EXISTING, 0, NULL)
			IFB _handleClient[name] = INVALID_HANDLE_VALUE THEN
				RESULT = GetLastError()
				Log("Err: CreateFile: " + RESULT)
			ENDIF
		ELSE
			RESULT = GetLastError()
			IF !RESULT THEN RESULT = -1	// TIMEOUT
			Log("Err: WaitNamedPipe: " + RESULT)
		ENDIF
	FEND

	FUNCTION ReadString(server = TRUE, name = DEF_NAME)
		DIM handle
		IFB server THEN
			handle = _handleServer[name]
		ELSE
			handle = _handleClient[name]
		ENDIF
		CONST SIZE = 1024
		DIM buf[SIZE], ret
		// 本来ならバッファサイズ以上での続行処理が必要
		IFB ReadFile(handle, buf, SIZE, ret, NULL) THEN
			RESIZE(buf, ret)
			RESULT = ConvertByte(buf)
		ELSE
			RESULT = GetLastError()
			IF RESULT THEN Log("Err: ReadFile: " + RESULT)
		ENDIF
	FEND

	FUNCTION WriteString(data, server = TRUE, name = DEF_NAME)
		DIM handle
		IFB server THEN
			handle = _handleServer[name]
		ELSE
			handle = _handleClient[name]
		ENDIF
		DIM len = LENGTH(data) * 2
		DIM buf[len]
		IFB ConvertByte(buf, data) THEN
			DIM ret
			// 本来なら少量書込みでの続行処理が必要
			IFB WriteFile(handle, buf, len, ret, NULL) and ret = len THEN
				ret = 0
			ELSE
				ret = GetLastError()
				IF ret THEN Log("Err: WriteFile: " + ret)
			ENDIF
			RESULT = ret
		ELSE
			RESULT = -1
		ENDIF
	FEND


	FUNCTION ConvertByte(var bdata[], data = EMPTY, dir = 0)
		// dir: 1(string->byte[]) 2(byte[]->string)
		IFB dir <> 1 and dir <> 2 THEN
			IFB data = NULL or data = EMPTY or LENGTH(data) = 0 THEN
				dir = 2
			ELSE
				dir = 1
			ENDIF
		ENDIF
		DIM ret = FALSE
		IFB dir = 1 THEN
			ret = ConvertByteFromString(bdata, data)
		ELSEIF dir = 2 THEN
			ret = ConvertByteToString(bdata)
		ENDIF
		RESULT = ret
	FEND
	FUNCTION ConvertByteFromString(var bdata[], data)
		DIM m = LENGTH(data), i
		DIM b, t
		FOR i = 1 TO m
			t = ASC(COPY(data, i, 1))
			b = t MOD 256
			bdata[(i - 1) * 2] = VARTYPE(b, VAR_BYTE)
			b = (t - b) / 256
			bdata[(i - 1) * 2 + 1] = VARTYPE(b, VAR_BYTE)
		NEXT
		RESULT = TRUE
	FEND
	FUNCTION ConvertByteToString(bdata[])
		DIM m = LENGTH(bdata) / 2 - 1, i
		DIM ret = ""
		FOR i = 0 TO m
			ret = ret + CHR(bdata[i*2] + bdata[i*2+1] * 256)
		NEXT
		RESULT = ret
	FEND


	FUNCTION Log(msg, t=0)
		DIM n = 0
		IFB _log THEN
			n = GETTIME() * 1000 + G_TIME_ZZ
			DIM buf = G_TIME_NN2 + ":" + G_TIME_SS2 + "." + G_TIME_ZZ2 + " " + msg
			IF t > 0 THEN buf = buf + "(" + (n - t) + ")"
			PRINT buf
		ENDIF
		RESULT = n
	FEND

ENDMODULE

注意点と今後の課題

スクリプト中にも書きましたが、スレッドがAPI中でブロックしている時に、
メインでINPUTを出すスクリプトでは、UWSCが無反応になりました。
スクリプトで交互に通信するのはOKだったのですが、、、。
ということで、とりあえず一方向にしています。
何が原因か不明なので、それ次第でもありますが、
場合によっては、Overlappedの助けを借りる必要がありそうです。
→DWORDの5個配列でよさげ(UWSCは32bitなので)


あと、なんとなくWin32 APIANSI版を使ってましたが、
UWSCだとUnicodeが便利な気がしたので、今回はUnicode版にしました。