send command to another program's "named pipe"

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
boir

send command to another program's "named pipe"

19 Oct 2015, 14:39

the winuae amiga emulator can accept commands from the Windows host through something called a named pipe.
The winuae log gives the name of the pipe

Code: Select all

IPC: Named Pipe '\\.\pipe\WinUAE' open
A forum post for winuae says that the strings to send should have this format

Code: Select all

EVT <event name> <state value>
where <event name> <state value> are winuae specific commands. But how to send to a named pipe with autohotkey? I searched the forum and found this old thread but the code there is a bit over my head. Can someone explain more on how to do this or show how to make a function that takes a pipe name variable and a command variable as input and sends that to the named pipe?
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: send command to another program's "named pipe"

19 Oct 2015, 16:16

See if this works (with appropriate EVT parameters):

Code: Select all

FileOpen("\\.\pipe\WinUAE", "w").Write("EVT <event name> <state value>")
boir

Re: send command to another program's "named pipe"

21 Oct 2015, 17:21

With the right parameters that does send a command successfully. But subsequent runs fail. The pipe can't be open. The user chaos at the winUAE forum wrote this python + pywin32 code that works also after the first run.

Code: Select all

import sys, os
from win32file import *
# try to open WinUAE pipe, or exit
try:
  up = CreateFile(r'\\.\pipe\WinUAE', GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, 0, None)
except:
  print "Can't open WinUAE pipe."
  sys.exit(-1)
# join all command line args (except scriptname)
args = " ".join(sys.argv[1:]) + "\0"
# write args to WinUAE pipe
WriteFile(up, args)
# output return code
print ReadFile(up, 4096)[1]
# close handle
CloseHandle(up)
Is that reproducible in Autohotkey?
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: send command to another program's "named pipe"

21 Oct 2015, 23:21

The problem is probably related to WinUAE writing the response code to the pipe, since my line of code doesn't read it. What is the exact format of the response?

Try something like this:

Code: Select all

up := FileOpen("\\.\pipe\WinUAE", "rw")
up.Write("EVT <event name> <state value>")
response := up.Read(4096)
up.Close()
MsgBox % response
It's not exactly equivalent, since the File object does buffering and handles various file encodings. I suspect this actually won't work, because FileOpen() tries to read the "file" (when opened with the "r" flag) in order to check for a UTF-8 or UTF-16 byte order mark.

If it works, you probably don't need to close the pipe after each Write/Read; just open it once and re-use.

CreateFile, WriteFile, ReadFile and CloseHandle can be called via DllCall, but the File object is generally easier, if it works.
boir

Re: send command to another program's "named pipe"

22 Oct 2015, 03:58

Not working.

The WinUAE pipe should return codes like "501, "200" and "404" (404 when the command is successful).

So we need something like http://ahkscript.org/docs/commands/DllCall.htm , example "This is a working script that writes some text to a file" then? I tried reusing some of commands but haven't gotten the hang of it.

I should say that the WinUAE dev also has a small test tool with c++ source for writing to the named pipe here http://www.winuae.net/files/stuff/ipctester.zip if that helps.
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: send command to another program's "named pipe"

22 Oct 2015, 07:38

By "the exact format of the response," I mean details like:
  • The binary encoding: is it a binary integer, an ASCII string of digits, UTF-8, UTF-16, a custom binary format...?
  • Are there any delimiters or terminators.
After experimenting and reading the source code, it seems the response is a UTF-8 string, UTF-16 string or some other 8-bit string, depending on whether the message began with a UTF-8 or UTF-16 byte order mark. The response begins with a string of digits which may or may not be followed by a newline character -- this inconsistency is a problem. It is terminated with a null character.

I've found that there are two problems:

Problem 1: As I suspected, FileOpen("\\.\pipe\WinUAE", "rw") will not work for this purpose, because it attempts to read the "file" immediately. Since WinUAE doesn't write anything until after you write a message, this attempt blocks indefinitely. One solution is to manually open the pipe using CreateFile and then pass the handle to FileOpen():

Code: Select all

; up = CreateFile(r'\\.\pipe\WinUAE', GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, 0, None)
hup := DllCall("CreateFile", "str", "\\.\pipe\WinUAE", "int", 0x80000000 | 0x40000000
    , "int", 0, "ptr", 0, "int", 0x3, "int", 0, "ptr", 0, "ptr")
if (hup = -1)
    throw "Can't open WinUAE pipe."
up := FileOpen(hup, "h")
From there you can use Write() as before.

By contrast, my initial post used only the "w" flag. FileOpen() assumes that it's a normal file which is being created or overwritten (becoming empty), so doesn't check for a byte order mark. It also can't check, because it doesn't request read access.

Problem 2: Although up.Read(4096) isn't required to read 4096 characters (that's an upper limit), it does try to read more than is available. Internally, it calls ReadFile() once to pull data into its buffer, and again to verify that there's no more data available. If ReadFile() returns 0 bytes, the end of the "file" has been reached. For a pipe, ReadFile() won't return until either some data is available or the pipe has been closed.

This appears to work (in combination with the above):

Code: Select all

up.Write("ipc_config")
MsgBox % up.ReadLine()  ; 200
I'm able to send this command multiple times (using the same connection) or run the script repeatedly (reconnecting each time). However, this will only work if the response is always terminated with a newline character. Unfortunately, that is not the case.

So basically, it's best to do it all via DllCall.

Alternative: There's actually a Win32 function specifically for situations like this:
CallNamedPipe function
Connects to a message-type pipe (and waits if an instance of the pipe is not available), writes to and reads from the pipe, and then closes the pipe.

Code: Select all

MsgBox % WinUAE("ipc_config")

WinUAE(command) {
    VarSetCapacity(result, 4096)
    if !DllCall("CallNamedPipe", "str", "\\.\pipe\WinUAE"
        , "astr", command, "int", StrPut(command, "cp0")
        , "ptr", &result, "int", 4096
        , "uint*", bytesRead, "uint", 1)
        throw Exception("CallNamedPipe failed with error " A_LastError)
    return RegExReplace(StrGet(&result, bytesRead, "cp0"), "\R$")
}
The message is passed as an 8-bit string (using your system default ANSI code page), so the result is also an 8-bit string. I'm assuming that it is an ANSI or ASCII string, since it's neither UTF-8 nor UTF-16, but haven't looked far enough into the source code to confirm this.

RegExReplace removes any trailing newline character.

StrGet copies the string from the buffer, converting it from ANSI to UTF-16 if you're using a Unicode version of AutoHotkey. My first version of the function did not require it, because WinUAE purportedly supports UTF-16 directly:

Code: Select all

WinUAE(command) {
    if A_IsUnicode
        command := Chr(0xFEFF) command ; UTF-16 BOM
    VarSetCapacity(result, 4096, 0)
    if !DllCall("CallNamedPipe", "str", "\\.\pipe\WinUAE"
        , "str", command, "int", (StrLen(command)+1)*(A_IsUnicode?2:1)
        , "str", result, "int", 4096
        , "uint*", bytesRead, "uint", 1)
        throw Exception("CallNamedPipe failed with error " A_LastError)
    return RegExReplace(result, "\R$")
}
However, this version did not work correctly on the Unicode build because of a bug in WinUAE:

Code: Select all

outlen = _tcsclen ((TCHAR*)ipc->outbuf) + sizeof (TCHAR);
For a string of n characters, the code writes n+2 bytes. Since each TCHAR is 2 bytes, the result is truncated. The developers of WinUAE might like to know about this error. Correct code might look like:

Code: Select all

outlen = (_tcsclen ((TCHAR*)ipc->outbuf) + 1) * sizeof (TCHAR);
boir

Re: send command to another program's "named pipe"

22 Oct 2015, 13:40

lexikos, you are awesome :) Thanks a bunch for these detailed tests and the working version! I've tried to "stress" the function and send commands in quick sequence but it is reliable. But I now feel that I've used up too much of your time on this. Hopefully this post can be useful also for others who want to write to named pipes in Autohotkey in the future. I will notify the WinUAE developer about the bug.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Billykid and 143 guests