1- import win32pipe , win32file , win32api , winerror , win32con , pywintypes
1+ import ctypes
2+ from ctypes import wintypes
23from os import path
34import io
45from typing import TypeVar , NewType , Literal , IO , Optional , Union
56
67WritableBuffer = TypeVar ("WritableBuffer" )
78PyHANDLE = NewType ("PyHANDLE" , int )
89
10+ # Define global constants
11+ PIPE_ACCESS_DUPLEX = 0x00000003
12+ PIPE_ACCESS_INBOUND = 0x00000001
13+ PIPE_ACCESS_OUTBOUND = 0x00000002
14+ PIPE_TYPE_BYTE = 0x00000000
15+ PIPE_READMODE_BYTE = 0x00000000
16+ PIPE_WAIT = 0x00000000
17+ PIPE_UNLIMITED_INSTANCES = 0xFFFFFFFF
18+ ERROR_PIPE_CONNECTED = 535
19+ ERROR_BROKEN_PIPE = 109
20+ ERROR_MORE_DATA = 234
21+ ERROR_IO_PENDING = 997
22+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
23+ INVALID_HANDLE_VALUE = - 1
24+
925id = 0
1026
27+ def _wt (value : int ) -> wintypes .DWORD :
28+ return wintypes .DWORD (value )
1129
1230def _name_pipe ():
1331 global id
@@ -23,11 +41,8 @@ def _name_pipe():
2341
2442def _win_error (code = None ):
2543 if not code :
26- code = win32api .GetLastError ()
27- return OSError (
28- f"[OS Error { code } ] { win32api .FormatMessage (win32con .FORMAT_MESSAGE_FROM_SYSTEM ,0 ,code ,0 ,None )} "
29- )
30-
44+ code = ctypes .get_last_error ()
45+ return ctypes .WinError (code )
3146
3247class NPopen :
3348 def __init__ (
@@ -44,6 +59,7 @@ def __init__(
4459 if not isinstance (bufsize , int ):
4560 raise TypeError ("bufsize must be an integer" )
4661
62+ self .kernel32 = ctypes .WinDLL ('kernel32' , use_last_error = True )
4763 self .stream : Union [IO , None ] = None # I/O stream of the pipe
4864 self ._path = _name_pipe () if name is None else rf"\\.\pipe\{ name } "
4965 self ._rd = any (c in mode for c in "r+" )
@@ -72,30 +88,25 @@ def __init__(
7288 )
7389
7490 self ._bufsize = - 1 if txt_mode and bufsize == 1 else bufsize
75-
7691 if self ._rd and self ._wr :
77- access = win32pipe . PIPE_ACCESS_DUPLEX
92+ access = _wt ( PIPE_ACCESS_DUPLEX )
7893 elif self ._rd :
79- access = win32pipe . PIPE_ACCESS_INBOUND
94+ access = _wt ( PIPE_ACCESS_INBOUND )
8095 elif self ._wr :
81- access = win32pipe . PIPE_ACCESS_OUTBOUND
96+ access = _wt ( PIPE_ACCESS_OUTBOUND )
8297 else :
8398 raise ValueError ("Invalid mode" )
8499 # TODO: assess options: FILE_FLAG_WRITE_THROUGH, FILE_FLAG_OVERLAPPED, FILE_FLAG_FIRST_PIPE_INSTANCE
100+ pipe_mode = _wt (PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT )
85101
86- pipe_mode = (
87- win32pipe .PIPE_TYPE_BYTE
88- | win32pipe .PIPE_READMODE_BYTE
89- | win32pipe .PIPE_WAIT
90- )
91102 # TODO: assess options: PIPE_WAIT, PIPE_NOWAIT, PIPE_ACCEPT_REMOTE_CLIENTS, PIPE_REJECT_REMOTE_CLIENTS
92103
93- max_instances = win32pipe . PIPE_UNLIMITED_INSTANCES # 1
94- buffer_size = 0
95- timeout = 0
104+ max_instances = _wt ( 1 ) # PIPE_UNLIMITED_INSTANCES returns 'invalid params'. Pipes are point-to-point anyway
105+ buffer_size = _wt ( 0 )
106+ timeout = _wt ( 0 )
96107
97108 # "open" named pipe
98- self . _pipe = win32pipe . CreateNamedPipe (
109+ h = self . kernel32 . CreateNamedPipeW (
99110 self ._path ,
100111 access ,
101112 pipe_mode ,
@@ -105,37 +116,37 @@ def __init__(
105116 timeout ,
106117 None ,
107118 )
108- if self . _pipe == win32file . INVALID_HANDLE_VALUE :
119+ if h == INVALID_HANDLE_VALUE :
109120 raise _win_error ()
121+ self ._pipe = h
110122
111123 @property
112124 def path (self ):
113125 """str: path of the pipe in the file system"""
114126 return self ._path
115127
116128 def __str__ (self ):
117- # return the path
118129 return self ._path
119130
120131 def close (self ):
121- # close named pipe
132+ """Close the named pipe.
133+ A closed pipe cannot be used for further I/O operations. `close()` may
134+ be called more than once without error.
135+ """
122136 if self .stream is not None :
123137 self .stream .close ()
124138 self .stream = None
125139 if self ._pipe is not None :
126- if win32file .CloseHandle (self ._pipe ):
127- raise _win_error ()
140+ self .kernel32 .CloseHandle (self ._pipe )
128141 self ._pipe = None
129142
130143 def wait (self ):
131-
144+ """Wait for the pipe to open (the other end to be opened) and return file object to read/write."""
132145 if not self ._pipe :
133146 raise RuntimeError ("pipe has already been closed." )
134-
135- # wait for the pipe to open (the other end to be opened) and return fileobj to read/write
136- if win32pipe .ConnectNamedPipe (self ._pipe , None ):
137- code = win32api .GetLastError ()
138- if code != 535 : # ERROR_PIPE_CONNECTED (ok, just indicating that the client has already connected)(Issue#3)
147+ if not self .kernel32 .ConnectNamedPipe (self ._pipe , None ):
148+ code = ctypes .get_last_error ()
149+ if code != ERROR_PIPE_CONNECTED : # (ok, just indicating that the client has already connected)(Issue#3)
139150 raise _win_error (code )
140151
141152 # create new io stream object
@@ -179,15 +190,16 @@ def writable(self) -> bool:
179190class Win32RawIO (io .RawIOBase ):
180191 """Raw I/O stream layer over open Windows pipe handle.
181192
182- `handle` is an open ``pywintypes.PyHANDLE `` object (from ``pywin32 `` package) to
193+ `handle` is an open Windows ``HANDLE `` object (from ``ctype `` package) to
183194 be wrapped by this class.
184195
185196 Specify the read and write modes by boolean flags: ``rd`` and ``wr``.
186197 """
187198
188199 def __init__ (self , handle : PyHANDLE , rd : bool , wr : bool ) -> None :
189200 super ().__init__ ()
190- self .handle = handle # PyHANDLE: Underlying Windows handle.
201+ self .kernel32 = ctypes .WinDLL ('kernel32' , use_last_error = True )
202+ self .handle = handle # Underlying Windows handle.
191203 self ._readable : bool = rd
192204 self ._writable : bool = wr
193205
@@ -211,7 +223,7 @@ def close(self) -> None:
211223 if self .closed :
212224 return
213225 if self .handle is not None :
214- win32file .CloseHandle (self .handle )
226+ self . kernel32 .CloseHandle (self .handle )
215227 self .handle = None
216228
217229 super ().close ()
@@ -224,23 +236,19 @@ def readinto(self, b: WritableBuffer) -> Union[int, None]:
224236 assert self ._readable
225237
226238 size = len (b )
227- nread = 0
228- while nread < size :
229- try :
230- hr , res = win32file .ReadFile (self .handle , size - nread )
231- if hr in (winerror .ERROR_MORE_DATA , winerror .ERROR_IO_PENDING ):
232- raise _win_error (hr )
233- except pywintypes .error as e :
234- if e .args [0 ] == 109 : # broken pipe error
235- break
236- else :
237- raise e
238- if not len (res ):
239- break
240- nnext = nread + len (res )
241- b [nread :nnext ] = res
242- nread = nnext
243- return nread
239+ nread = _wt (0 )
240+ buf = (ctypes .c_char * size ).from_buffer (b )
241+
242+ success = self .kernel32 .ReadFile (self .handle , buf , size , ctypes .byref (nread ), None )
243+ if not success :
244+ code = ctypes .get_last_error ()
245+ # ERROR_MORE_DATA - not big deal, will read next time
246+ # ERROR_IO_PENDING - should not happen, unless use OVERLAPPING, which we don't so far
247+ # ERROR_BROKEN_PIPE - pipe was closed from other end. While it is an error, test seemingly expects to receive 0 instead of exception
248+ if code not in (ERROR_MORE_DATA , ERROR_IO_PENDING , ERROR_BROKEN_PIPE ):
249+ raise _win_error (code )
250+
251+ return nread .value
244252
245253 def write (self , b ):
246254 """Write buffer ``b`` to file, return number of bytes written.
@@ -250,7 +258,10 @@ def write(self, b):
250258
251259 assert self .handle is not None # show type checkers we already checked
252260 assert self ._writable
253- hr , n_written = win32file .WriteFile (self .handle , b )
254- if hr :
255- raise _win_error (hr )
256- return n_written
261+ size = len (b )
262+ nwritten = _wt (0 )
263+ buf = (ctypes .c_char * size ).from_buffer_copy (b )
264+ if not self .kernel32 .WriteFile (self .handle , buf , _wt (size ), ctypes .byref (nwritten ), None ):
265+ raise _win_error ()
266+
267+ return nwritten .value
0 commit comments