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问题详解

安装Pygame(Python3.6,windows)

Python语法速查:2x. 杂项

如何在环境变量PATH中搜索和替换字符串?

windows环境配置:同时安装Python2.7和Python3.6开发环境

Python语言的应用领域