47

What is the proper way of communicating with named pipes on Windows from Python? I've googled it, and can't find any packages that wrap this communication.

There are:

I need just to connect to an existing named pipe and read/write to it. I previously had only tried communication with serial port (using pySerial), and I'm surprised how little info I could find on named pipes in comparison to it. There's usually tons of guides for any purpose for Python.

I'll appreciate any help.

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
Ray P.
  • 875
  • 1
  • 9
  • 24
  • 1
    You should be able to use `f = open(r'\\.\PIPE\', 'rb+', buffering=0)`, and then call `f.read(nbytes)` and `f.write(string)`. – Eryk Sun Feb 01 '18 at 00:50
  • 1
    @eryksun your recommendation as along the lines with the third solution from my list, except that it's 'r+b', and you need to write bytes to file, not string. I myself was hoping that it is the right way to go, and indeed managed to establish communication with a named pipe this way. My previous mistake with it was that I sent ascii control codes the wrong way. – Ray P. Feb 01 '18 at 07:17
  • "rb+" and "r+b" are the same. Also, your question is tagged for Python 2 and makes no mention of Python 3, so naturally I assumed you were using Python 2, in which `str` strings are byte strings. In Python 3 you need to write `bytes`. – Eryk Sun Feb 01 '18 at 20:05
  • Got it. I didn't tag it specifically for Python 2, just for Python. – Ray P. Feb 02 '18 at 06:17
  • True, the Python tag can go both ways, but I still expect Python 3 questions to be tagged "Python-3.x". Mentally I suppose it's because `python` on most platforms is still Python 2, and running Python 3 requires `python3`. – Eryk Sun Feb 02 '18 at 08:32

2 Answers2

58

In order to connect to an existing named pipe you can utilize the CreateFile API provided through the pywin32 package. Since it took me a while to put a working base together here is an example client/server which works fine for me (python 3.6.5, pywin32 223 on Windows 10 Pro x64):

import time
import sys
import win32pipe, win32file, pywintypes


def pipe_server():
    print("pipe server")
    count = 0
    pipe = win32pipe.CreateNamedPipe(
        r'\\.\pipe\Foo',
        win32pipe.PIPE_ACCESS_DUPLEX,
        win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT,
        1, 65536, 65536,
        0,
        None)
    try:
        print("waiting for client")
        win32pipe.ConnectNamedPipe(pipe, None)
        print("got client")

        while count < 10:
            print(f"writing message {count}")
            # convert to bytes
            some_data = str.encode(f"{count}")
            win32file.WriteFile(pipe, some_data)
            time.sleep(1)
            count += 1

        print("finished now")
    finally:
        win32file.CloseHandle(pipe)


def pipe_client():
    print("pipe client")
    quit = False

    while not quit:
        try:
            handle = win32file.CreateFile(
                r'\\.\pipe\Foo',
                win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                0,
                None,
                win32file.OPEN_EXISTING,
                0,
                None
            )
            res = win32pipe.SetNamedPipeHandleState(handle, win32pipe.PIPE_READMODE_MESSAGE, None, None)
            if res == 0:
                print(f"SetNamedPipeHandleState return code: {res}")
            while True:
                resp = win32file.ReadFile(handle, 64*1024)
                print(f"message: {resp}")
        except pywintypes.error as e:
            if e.args[0] == 2:
                print("no pipe, trying again in a sec")
                time.sleep(1)
            elif e.args[0] == 109:
                print("broken pipe, bye bye")
                quit = True


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("need s or c as argument")
    elif sys.argv[1] == "s":
        pipe_server()
    elif sys.argv[1] == "c":
        pipe_client()
    else:
        print(f"no can do: {sys.argv[1]}")

Example output client

> python pipe_test.py c
pipe client
no pipe, trying again in a sec
no pipe, trying again in a sec
no pipe, trying again in a sec
message: (0, b'0')
message: (0, b'1')
message: (0, b'2')
message: (0, b'3')
message: (0, b'4')
message: (0, b'5')
message: (0, b'6')
message: (0, b'7')
message: (0, b'8')
message: (0, b'9')
broken pipe, bye bye

Example output server

> python pipe_test.py s
pipe server
waiting for client
got client
writing message 0
writing message 1
writing message 2
writing message 3
writing message 4
writing message 5
writing message 6
writing message 7
writing message 8
writing message 9
finished now

Obviously you'd need some error checking around the various calls but that should work.

Additional side note: A colleague of mine ran into trouble with the pipe being closed the moment the client tried to perform I/O on it (exception claiming that "all pipe instances are busy"). It turned out that he was using os.path.exists in the client code to test whether the named pipe already existed before running CreateFile on it. This somehow breaks the pipe. So using the approach above (CreateFile wrapped in a try-except) is the safe way of trying to connect to a pipe until it has been created by the server end.

ChrisWue
  • 18,612
  • 4
  • 58
  • 83
  • 6
    great! this is only pipe example i could find running on windows with python 3+ – Finn Mar 25 '19 at 12:17
  • Don't you have to call some `CloseHandle()` on files created with `win32file.CreateFile()`? Is there a way to do this with `with`? – Serge Rogatch Jul 30 '20 at 05:02
  • 1
    @SergeRogatch Yeah, you should probably put the same `finally` clause into the client as it is already present for the server. – ChrisWue Jul 30 '20 at 06:14
  • Worked like charm on Win8.1 and `Py3.7.6`, after `pip install pywin32`. – not2qubit Nov 04 '20 at 17:45
6

I have success with something like the following fragment. This code is derived from CaptureSetup/Pipes — Python on Windows — The Wireshark Wiki. It requires win32pipe and win32file from the pywin32 package.

# pipename should be of the form \\.\pipe\mypipename
pipe = win32pipe.CreateNamedPipe(
        pipename,
        win32pipe.PIPE_ACCESS_OUTBOUND,
        win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_WAIT,
        1, 65536, 65536,
        300,
        None)
try:
    win32pipe.ConnectNamedPipe(pipe, None)

    while True:
        some_data = b'12345...'
        win32file.WriteFile(pipe, some_data)
        ...
finally:
    win32file.CloseHandle(pipe)

I don't know if it's 100% correct in the way it closes the pipe.

You referred to The Perils and Triumphs of Being a Geek: Named Pipes between C# and Python—Jonathon Reinhart. I tried it, but it wasn't able to create the named pipe. I wonder if that code only works to open a named pipe that has already been created by another process.

Craig McQueen
  • 41,871
  • 30
  • 130
  • 181
  • 1
    A bit late but re: C# and Python. C# code is the server, python code is the client. The server has to be running and waiting for the client before the client can start. – cup Mar 10 '20 at 15:05