Windows 中的 python 2 和 python 3 之间的命名管道的工作方式有啥不同吗?
Posted
技术标签:
【中文标题】Windows 中的 python 2 和 python 3 之间的命名管道的工作方式有啥不同吗?【英文标题】:Is there a difference in how named pipes work between python 2 and python 3 in Windows?Windows 中的 python 2 和 python 3 之间的命名管道的工作方式有什么不同吗? 【发布时间】:2021-08-02 18:42:13 【问题描述】:我有一个简单的 exe 文件,它创建了用于与虚拟通信端口通信的命名管道。 Python 应用程序与命名端口通信,然后 exe 与 VCP 通信(将 exe 视为 VCP 前面的一对深度 FIFO)。 exe 在单独的进程中运行,用于提供大缓冲区并获得比在 Python 中执行此操作更好的性能。它只是连续运行,直到停止(被互斥体)在 Python 应用程序和 VCP 之间传递数据。它已使用多年,首先用于 Windows 7 和 Python 2.7,然后用于 Windows 10 和 Python 2.7。但是当我切换到 Python 3(使用 Win10)时,我无法再与命名管道通信。
我创建了一个 Python 程序(简称为 PythonEXE),它复制了 exe 的功能,在一个单独的进程中运行(用 popen 打开),它可以在 Python3 中运行。所以我知道命名管道在 Python 3 中工作,并且使用单独的进程不是某种问题(应用程序在所有地方都会这样做,所以如果发生这种情况我会有其他问题)。
我知道 Python 3 应用程序正在向命名管道发送字节(而不是字符串字符),因为首先,在与它们通信时我没有遇到任何异常,并且因为它可以与复制功能的 PythonEXE 程序一起使用的原始exe。这不是操作系统问题,因为只要我使用的是 Pythion 2.7,同一个 exe 就适用于 Win7 和 Win10。
exe 不会崩溃,它会一直运行,直到它被告知关闭,它会优雅地执行此操作。我试过用 os.startfile 和 popen 打开它,两种方法的结果是一样的。
我知道有人会问的第一个问题是代码 sn-ps,我可以提供,但我最初的问题很简单:有谁知道 Python 2.7 和 Python 3 之间的变化(使用 3.8. 8)可以解释这种行为?命名管道在两个 Python 版本之间在 Windows 中的工作方式有什么不同吗?我现在很难过。我可以看到我正在发送正确的东西,但它在 Python 2 和 Python 3 之间的工作方式不同。相同的操作系统,相同的 EXE,没有崩溃,没有错误,就像发送到命名管道的东西一样进入一个黑洞。由于它适用于 python-to-python 命名管道(一端在 PythonEXE 中),不同之处在于 Python 3 与原始 exe 中创建的命名管道通信的方式(而不是与 PythonEXE 创建的命名管道通信) .
仅供参考:我有 exe 的源代码,但没有开发环境(已有多年历史)。在重新创建它以进行调试之前,我想确保 Python 版本 2 和 3 之间没有任何更改可以解释这一点并且可能很容易修复。
是的,我也许可以只使用 PythonEXE,但我必须进行大量测试以确保没有性能问题;我想使用现有的 EXE,因为它已经过测试并且我知道它可以工作。
我感谢任何可以说“哦,是的,我知道这是什么,你需要......”的人
好吧,好吧,如果你想要代码,我创建了一个调试程序来解决这个问题,它去掉了所有额外的代码来运行硬件和其他软件模块。但它仍然相当长:
# Simplest version of pipe problem demo on Python3, stripped of everything extraneous.
import os
import time
import traceback
import subprocess # Just for testing with the Python exe substitute.
import win32file as w32f
import win32pipe as w32p
import win32event
# Flags to indicate whether we're testing with the exe or the Python substitute, and whether we are
# opening the write-pipe (output to VCP) using os. or win32file. functions.
UseExe = 1 # If 0, use Python for everything, including Python substitute for exe. 1 = use exe.
UseOSfcns = 0 # If 1, use os.open, os.write, etc. for output pipe. 0 = use createfile/writefile
VCPhandle = 0
VCPportNumber = -1
# Module-global read data buffer.
VCPreadData = b''
VCPstring = "" # Incoming string from VCP.
MUhandle = 0 # Will be handle for mutex if exe is used.
# Bogus ID strings
VCPvalidIDlist = [b'Valid ID string 1', b'Valid ID string 2']
# Attempt to open port. Sets USBPIO to port handle if successful, 0 if not.
# Configures for 8 data bits, non-blocking, 0 timeout.
# USBportNumber gets set to port number.
# Returns 1 if successful, 0 if not.
def OpenPort(PortNumber):
global VCPhandle
result = 0
try:
VCPhandle = win32file.CreateFile("\\\\.\\COM" + str(PortNumber),
win32file.GENERIC_READ | win32file.GENERIC_WRITE,
0, None,
win32file.OPEN_EXISTING,
0, 0)
except:
#print("Could not open port - exception", PortNumber)
VCPhandle = 0
else:
# Configure port for 8 data bits, 0 timeout, non blocking.
# See if serial port is set to 7 bit size; if so, set to 8.
USBDCB = win32file.GetCommState(VCPhandle)
if USBDCB.ByteSize < 8:
USBDCB.ByteSize = 8
win32file.SetCommState(VCPhandle, USBDCB)
SetTimeouts(VCPhandle)
result = 1
return result
def ReadPort(size = 1000):
global VCPhandle
x, indata = win32file.ReadFile(VCPhandle, size)
return indata
# Python3 only, write to pipe. Check data type.
def Py3WritePipe(Data):
if type(Data) == str: DataOut = bytes(Data, "latin1")
elif type(Data) == bytes: DataOut = Data
elif type(Data) == int: DataOut = bytes([Data])
if UseOSfcns: result = os.write(PipeOut, DataOut)
else: result = w32f.WriteFile(PipeOut, DataOut)
return result
# Empty any incoming USB VCP data.
def PY3EmptyUSB():
global PipeIn, VCPreadData
done = 0
indata = ""
while done == 0:
# This is a bit funky. If ReadFile is called and the pipe is
# empty, it hangs. So PeekNamedPipe is used to see if there is
# data. if there isn't, ReadFile isn't called and the loop
# terminates. If there is data in the pipe (return element 1
# is available size) then empty the pipe.
if w32p.PeekNamedPipe(PipeIn, 0)[1] > 0:
x, indata = w32f.ReadFile(PipeIn, 1000)
else:
done = 1
x = 1
indata = ""
if len(indata): VCPreadData += indata
else: done = 1 # Empty read, exit.
def ClosePort():
global VCPhandle
try: win32file.CloseHandle(VCPhandle)
except: pass
VCPhandle = 0
# Shut down exe using mutex and file for Python pipe module.
def USBshutdown():
print("USB shutdown")
# Release mutex so exe will shut down.
try:
win32event.ReleaseMutex(MuHandle) # Relese mutex to kill exe.
except: pass
# If using Python exe substitute, write the file to tell it to close.
FH = open("DLPpipeShutdown.txt", "w")
FH.write(chr(1))
FH.close()
def CreateVCPmutex():
global MuHandle
# Create a mutex. When code exits, mutex is released, so exe program
# knows to exit as well. Without this, the exe would never exit.
# When exe can get this mutex, it knows Python code has exited and/or
# released mutex.
MuHandle = win32event.CreateMutex(None, True,"Global\\DDUSBPIOMUTEX")
win32event.WaitForSingleObject(MuHandle, 0)
# Note: SetTimeouts MUST be called after opening comm port but before
# using comm port. Otherwise port will hang waiting for data.
# This sets timeout so that read functions will return immediately
# if there is no data.
def SetTimeouts(commhandle, TimeoutStruct = (-1, 0, 0, 0, 0)):
win32file.SetCommTimeouts(commhandle, TimeoutStruct)
def GetTimeouts(commhandle):
result = win32file.GetCommTimeouts(commhandle)
return result
# Fin VCP port by opening comm ports one at a time and
# requesting/checking ID. Note that this does NOT use the pipes or the exe/python substitute.
# This walks through the comm ports looking for a valid VCP with a valid response.
# This code all works, finds the port, sets module-level globals for other code.
def FindVCP():
global VCPhandle, VCPportNumber, VCPstring, USBIDFWVer
PN = 0 # Port number to try
ValidIDfound = 0 # Flag to indicate valid ID located.
while PN < 32:
# OpenPort opens and configures port for comms.
if OpenPort(PN):
# Flush anything in the incoming FIFO.
try: VCPstring = ReadPort()
except: pass
# Get ID string. This was previously a function call, make it direct write for this simple case.
result = win32file.WriteFile(VCPhandle, bytes([13])) # Send request for board ID.
# Delay to allow plenty of time for a response. Performance is irrelevant here as we're just trying to find the VCP).
time.sleep(.2)
USBstring = ReadPort()
# See if there is a returned string and if it's from a valid VCP HW.
if len(USBstring) > 5:
for ValidID in VCPvalidIDlist:
if ValidID in USBstring[1:]:
VCPportNumber = PN
ValidIDfound = 1
# Convert the ID portion of the string (minus the returned ID request opcode)
# to ASCII.
print("FindVCP ID:", USBstring[1:].decode("ascii"))
break # Found the right port, exit the while loop.
if ValidIDfound: break
if ValidIDfound: break
# Normally a bunch of code goes here to configure things for specific hardware
# verisons/configurations. All stripped out here to simplify the example.
# Found a valid port, but it wasn't the VCP port we want. Close it and try the next one.
else:
ClosePort()
#print(" Port opened, but no response", PN)
#else: print(" Could not open port")
PN += 1 # Increment port number to try.
if __name__ == "__main__":
# This is used to signal the Python exe substitute module.
FH = open("DLPpipeShutdown.txt", "w")
FD = FH.write(chr(0))
FH.close()
FindVCP()
if VCPhandle: ClosePort()
# Now that we can see if we can find the comm port in Python, try with the exe file.
# Just simple walk LEDs to see if we can talk to it.
# Note that if UseExe is 0, then we use the DLL pipe substitute. The pipes and commands
# all work the same, it's just a matter of which one gets started and how things are
# passed in. The exe gets the port number from a file, the python "DLL" gets it
# passed in as a parameter. Shutdown uses a mutex for the exe, a value in a file for
# the Python code.
if UseExe:
print("Using EXE")
CreateVCPmutex()
FH = open("DDUSBPort.bin", "w")
FH.write(str(VCPportNumber))
FH.close()
# Tried both of these, neither works. That is, they don't fail but the pipes don't work.
os.startfile("USBPIOPipe.exe")
#PipeHandle = subprocess.Popen('USBPIOPipe.exe', creationflags=subprocess.CREATE_NEW_CONSOLE)
# Use the Python DLL substitute instead
else:
print("Using Python DLL substitute")
PipeHandle = subprocess.Popen('python DLPpipe.pyc ' + str(VCPportNumber),
creationflags=subprocess.CREATE_NEW_CONSOLE)
time.sleep(.5)
print("Creating pipeout")
if UseOSfcns: PipeOut = os.open(r"\\.\pipe\DDUSBPIPEOUT", os.O_WRONLY)
else:
#PipeOut = w32f.CreateFile(r"\\.\pipe\DDUSBPIPEOUT",
# w32f.GENERIC_WRITE,
# w32f.FILE_SHARE_WRITE | w32f.FILE_SHARE_READ,
# SecAttr,
# w32f.OPEN_EXISTING,
# 0, 0)
PipeOut = w32f.CreateFile(r"\\.\pipe\DDUSBPIPEOUT",
w32f.GENERIC_WRITE,
0,
None,
w32f.OPEN_EXISTING,
0, 0)
print("Creating pipein")
#PipeIn = w32f.CreateFile(r"\\.\pipe\DDUSBPIPEIN",
# w32f.GENERIC_READ,
# w32f.FILE_SHARE_READ | w32f.FILE_SHARE_WRITE,
# SecAttr,
# w32f.OPEN_EXISTING,
# 0, 0)
PipeIn = w32f.CreateFile(r"\\.\pipe\DDUSBPIPEIN",
w32f.GENERIC_READ,
0,
None,
w32f.OPEN_EXISTING,
0, 0)
# Shutdown file is also indicator of when pipe handles are created.
# Writing 1 means shut down. Writing 2 means pipe handles created and it's OK
# for the pipe process to execute ConnectNamedPipe on the pipe handles.
# This is only for Python DLL substitute; the exe file uses a mutex.
FH = open("DLPpipeShutdown.txt", "w")
FD = FH.write(chr(2)) # 2 = pipes created, ready to go code
FH.close()
time.sleep(1) # Delay a little for the file to catch up.
# Get ID string
Py3WritePipe(13) # Send request for board ID.
time.sleep(.2)
PY3EmptyUSB()
if len(VCPreadData): print("Using pipe:", VCPreadData.decode("ascii"))
else: print("Using Pipe, no data received from board ID request")
# Normally some test code would go here, but at this point I know that the
# interface (exe or python substitute) is working or not working based on getting
# back a valid ID string. Note that I've tried sending commands to activate hardware
# functions in the VCP, that do not require a response, and those don't work either.
# So the problem is that whatever is sent doesn't go through. In other words,
# the problem is sending, not receiving (although receiving could be broken as well).
# Shut down pipe in case we're using the Python exe substitute file.
FH = open("DLPpipeShutdown.txt", "w")
FD = FH.write(chr(1)) # 1 = shutdown code.
FH.close()
# If we were using the pipe exe, close the mutex so the exe closes.
if UseExe:
# Release mutex so EXE will shut down.
try:
print("Closing mutex")
time.sleep(1)
win32event.ReleaseMutex(MuHandle) # Relese mutex to kill exe.
win32event.ReleaseMutex(MuHandle) # Relese mutex to kill exe.
except: pass
time.sleep(.5)
【问题讨论】:
如果你有minimal reproducible example,请分享。大约 10 年 内任何可能影响这一点的变化都是……一个巨大的答案空间。 嗯,好的,就是这样。我创建了一个简单的测试程序来调试它,它消除了所有其他硬件和软件组件。它确实说明了问题,尽管我怀疑它对回答问题是否有用。 【参考方案1】:解决了。这是转换到 Python 3 的错误;我原以为它会抛出异常,但事实并非如此。所以我一直认为管道不工作了。
【讨论】:
以上是关于Windows 中的 python 2 和 python 3 之间的命名管道的工作方式有啥不同吗?的主要内容,如果未能解决你的问题,请参考以下文章
Windows下安装python2出现2503 2502问题详解