Skip to content

Commit 3a1f8f2

Browse files
author
Yaroshenko, Paul
committed
Ignore pyCharm stuff and symlink for local testing. Convert _win32 to ctype
1 parent f002518 commit 3a1f8f2

File tree

2 files changed

+72
-52
lines changed

2 files changed

+72
-52
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,5 @@ cython_debug/
171171

172172
# Built Visual Studio Code Extensions
173173
*.vsix
174+
/.idea
175+
/tests/namedpipe

src/namedpipe/_win32.py

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1-
import win32pipe, win32file, win32api, winerror, win32con, pywintypes
1+
import ctypes
2+
from ctypes import wintypes
23
from os import path
34
import io
45
from typing import TypeVar, NewType, Literal, IO, Optional, Union
56

67
WritableBuffer = TypeVar("WritableBuffer")
78
PyHANDLE = 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 = wintypes.HANDLE(-1).value
24+
925
id = 0
1026

27+
def _wt(value: int) -> wintypes.DWORD:
28+
return wintypes.DWORD(value)
1129

1230
def _name_pipe():
1331
global id
@@ -23,11 +41,18 @@ def _name_pipe():
2341

2442
def _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)}"
44+
code = ctypes.GetLastError()
45+
buf = ctypes.create_unicode_buffer(256)
46+
ctypes.FormatMessageW(
47+
FORMAT_MESSAGE_FROM_SYSTEM,
48+
None,
49+
code,
50+
0,
51+
buf,
52+
len(buf),
53+
None
2954
)
30-
55+
return OSError(f"[OS Error {code}] {buf.value}")
3156

3257
class NPopen:
3358
def __init__(
@@ -72,30 +97,26 @@ def __init__(
7297
)
7398

7499
self._bufsize = -1 if txt_mode and bufsize == 1 else bufsize
75-
76100
if self._rd and self._wr:
77-
access = win32pipe.PIPE_ACCESS_DUPLEX
101+
access = _wt(PIPE_ACCESS_DUPLEX)
78102
elif self._rd:
79-
access = win32pipe.PIPE_ACCESS_INBOUND
103+
access = _wt(PIPE_ACCESS_INBOUND)
80104
elif self._wr:
81-
access = win32pipe.PIPE_ACCESS_OUTBOUND
105+
access = _wt(PIPE_ACCESS_OUTBOUND)
82106
else:
83107
raise ValueError("Invalid mode")
84108
# TODO: assess options: FILE_FLAG_WRITE_THROUGH, FILE_FLAG_OVERLAPPED, FILE_FLAG_FIRST_PIPE_INSTANCE
109+
pipe_mode = _wt(PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT)
85110

86-
pipe_mode = (
87-
win32pipe.PIPE_TYPE_BYTE
88-
| win32pipe.PIPE_READMODE_BYTE
89-
| win32pipe.PIPE_WAIT
90-
)
91111
# TODO: assess options: PIPE_WAIT, PIPE_NOWAIT, PIPE_ACCEPT_REMOTE_CLIENTS, PIPE_REJECT_REMOTE_CLIENTS
92112

93-
max_instances = win32pipe.PIPE_UNLIMITED_INSTANCES # 1
94-
buffer_size = 0
95-
timeout = 0
113+
max_instances = _wt(PIPE_UNLIMITED_INSTANCES)
114+
buffer_size = _wt(0)
115+
timeout = _wt(0)
116+
96117

97118
# "open" named pipe
98-
self._pipe = win32pipe.CreateNamedPipe(
119+
self._pipe = ctypes.windll.kernel32.CreateNamedPipeW(
99120
self._path,
100121
access,
101122
pipe_mode,
@@ -105,7 +126,7 @@ def __init__(
105126
timeout,
106127
None,
107128
)
108-
if self._pipe == win32file.INVALID_HANDLE_VALUE:
129+
if self._pipe == INVALID_HANDLE_VALUE:
109130
raise _win_error()
110131

111132
@property
@@ -114,28 +135,28 @@ def path(self):
114135
return self._path
115136

116137
def __str__(self):
117-
# return the path
118138
return self._path
119139

120140
def close(self):
121-
# close named pipe
141+
"""Close the named pipe.
142+
A closed pipe cannot be used for further I/O operations. `close()` may
143+
be called more than once without error.
144+
"""
122145
if self.stream is not None:
123146
self.stream.close()
124147
self.stream = None
125148
if self._pipe is not None:
126-
if win32file.CloseHandle(self._pipe):
149+
if not ctypes.windll.kernel32.CloseHandle(self._pipe):
127150
raise _win_error()
128151
self._pipe = None
129152

130153
def wait(self):
131-
154+
"""Wait for the pipe to open (the other end to be opened) and return file object to read/write."""
132155
if not self._pipe:
133156
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)
157+
if not ctypes.windll.kernel32.ConnectNamedPipe(self._pipe, None):
158+
code = ctypes.GetLastError()
159+
if code != ERROR_PIPE_CONNECTED: # (ok, just indicating that the client has already connected)(Issue#3)
139160
raise _win_error(code)
140161

141162
# create new io stream object
@@ -179,15 +200,15 @@ def writable(self) -> bool:
179200
class Win32RawIO(io.RawIOBase):
180201
"""Raw I/O stream layer over open Windows pipe handle.
181202
182-
`handle` is an open ``pywintypes.PyHANDLE`` object (from ``pywin32`` package) to
203+
`handle` is an open Windows ``HANDLE`` object (from ``ctype`` package) to
183204
be wrapped by this class.
184205
185206
Specify the read and write modes by boolean flags: ``rd`` and ``wr``.
186207
"""
187208

188209
def __init__(self, handle: PyHANDLE, rd: bool, wr: bool) -> None:
189210
super().__init__()
190-
self.handle = handle # PyHANDLE: Underlying Windows handle.
211+
self.handle = handle # Underlying Windows handle.
191212
self._readable: bool = rd
192213
self._writable: bool = wr
193214

@@ -211,7 +232,7 @@ def close(self) -> None:
211232
if self.closed:
212233
return
213234
if self.handle is not None:
214-
win32file.CloseHandle(self.handle)
235+
ctypes.windll.kernel32.CloseHandle(self.handle)
215236
self.handle = None
216237

217238
super().close()
@@ -224,23 +245,16 @@ def readinto(self, b: WritableBuffer) -> Union[int, None]:
224245
assert self._readable
225246

226247
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
248+
nread = _wt(0)
249+
buf = (ctypes.c_char * size).from_buffer(b)
250+
251+
success = ctypes.windll.kernel32.ReadFile(self.handle, buf, size, ctypes.byref(nread), None)
252+
if not success:
253+
code = ctypes.GetLastError()
254+
if code not in (ERROR_MORE_DATA, ERROR_IO_PENDING):
255+
raise _win_error(code)
256+
257+
return nread.value
244258

245259
def write(self, b):
246260
"""Write buffer ``b`` to file, return number of bytes written.
@@ -250,7 +264,11 @@ def write(self, b):
250264

251265
assert self.handle is not None # show type checkers we already checked
252266
assert self._writable
253-
hr, n_written = win32file.WriteFile(self.handle, b)
254-
if hr:
255-
raise _win_error(hr)
256-
return n_written
267+
size = len(b)
268+
nwritten = wintypes.DWORD(0)
269+
buf = (ctypes.c_char * size).from_buffer_copy(b)
270+
if not ctypes.windll.kernel32.WriteFile(self.handle, buf, size, ctypes.byref(nwritten), None):
271+
code = ctypes.GetLastError()
272+
raise _win_error(code)
273+
274+
return nwritten.value

0 commit comments

Comments
 (0)