在Windows上,我可以使用什么来代替Unix管道进行进程间通信



我有一个启动子流程的应用程序。子进程从stdin读取要操作的文件。对于某些操作,它需要一个输入文件,其中包含如何处理它操作的文件的信息——让我们称之为"控制文件"。控制文件的名称也从stdin中读取。父应用程序可以使用临时文件作为控制文件,但我更希望避免使用真正的磁盘备份文件。

在Linux上,这很简单:我可以创建一个Unix管道,fork,在启动子进程之前关闭管道的各个末端,并使用/dev/fd/3(或任何文件描述符)作为控制文件名,然后将控制数据写入父应用程序中的管道。或者,我可以在/tmp中使用一个命名管道(或其他)。

如何在Windows上实现类似的功能?Windows提供的奇怪的"命名管道"可以用于此吗?也就是说,它们可以由通常的C库fread()函数读取吗?如果是,我应该使用什么文件名来访问它们?或者有比使用命名管道更好的方法吗?

(子进程是以批处理模式运行的exiftool命令行实用程序,所以我无法控制它。父应用程序是用Python编写的。)

更新:

@Harry Johnston指出,我误解了你的问题——你不想修改子流程。在这种情况下,您可以尝试调用CreateProcess,并用CreateNamedPipe中的HANDLE填充STARTUPINFOhStdInput成员。

上一个答案:

一般来说,Windows CRT(C运行时,或Unix中的"libc")是一个奇怪的野兽:它是C标准库的一个非常"基本"的填充,添加了一些额外的东西,维护得不好,也没有暴露出Windows的很多功能。用C编写Windows软件最自然的方法是Win32 API。也就是说:

Windows提供的奇怪的"命名管道"可以用于此吗?也就是说,它们可以由通常的C库fread()函数读取吗

是的,我相信你可以用_open_osfhandle做到这一点,其中第一个参数可以是HANDLE。这会给你一个整数,这是Windows CRT对Unix文件描述符的奇怪嘲弄。然后你可以用_fdopen得到一个FILE*

如果是,我应该使用什么文件名来访问它们

我想你可以尝试生成一个不碰撞的随机的。也许可以在它前面加上应用程序的名称,并使用进程ID和当前时间的组合?这只是我要扔掉的东西。

或者有比使用命名管道更好的方法吗

您可以在AF_UNIX系列中使用套接字,尽管它最终会非常相似。。。

您可以使用PowerShell作为批处理子进程来创建命名管道,并使用它在Windows上的批处理和其他批处理/子系统之间构建IPC。

下面是一个用于快速着色批处理输出的示例:PowerShell作为通过命名管道的子进程

编辑:将其设置为消息模式,使@Bbb 更可读

EDIT2:添加丢失的NOP和安全警告。

小心安全问题。使用命名管道的凭据,并注意服务器上的任何控制台/进程都可以读取fd 5。使用真正的双向管道或其他命名管道,而不是fd 5来避免这种情况。不要用这个"照原样";在野外

示例批次";代码";以创建";双向";通过PowerShell的命名管道:

::
:: Launch a PowerShell child process in the background linked to the console and 
:: earing through named pipe which name provided as an argument of this function.
::
:: Parameters :
::   [ Name] : A name for the named pipe.
:: Return :
::   0 if the child PowerShell has been successfully launched and the named pipe is available.
::   1 if it fails.
::   3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcessPipe
  SET LOCALV_NAME=
  IF NOT "%~1" == "" SET "LOCALV_NAME=%~1"
  IF "%~1" == "" ( ECHO :LaunchPowerShellSubProcessPipe need a name.& EXIT /B 1)
  powershell -command "$_" 2>&1 >NUL
  IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
  REM As properly stated by WReach [https://mathematica.stackexchange.com/questions/198187/how-to-read-a-named-pipe-on-windows], Windows named pipe have their own hand shake logic (WaitNamedPipe... {humor}Microsoft hate us all probably{/humor}
  REM To "read" the named pipe from batch in a POSIX way, we redirected reply to fd 5. Reply to the output part of the named pipe can't be read from batch directly nor with common windows tools. 
  REM Do not use [System.IO.Pipes.PipeOptions]::Asynchronous ! 
  (ECHO $CloseHandle = Add-Type 'A' -PassThru -MemberDefinition '[DllImport^^^("kernel32.dll"^^^)] public static extern bool CloseHandle^^^(IntPtr hObject^^^);'; & ^
ECHO # In case of, we close the fd beforehand & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream^^^('%LOCALV_NAME%', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256^^^); & ^
ECHO Try { & ^
ECHO   $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO   $pipeReader = new-object System.IO.StreamReader^^^($npipeServer^^^); & ^
ECHO   Try { & ^
ECHO     While^^^(^^^($msg = $pipeReader.ReadLine^^^(^^^)^^^) -notmatch 'QUIT'^^^) { & ^
ECHO       Try { & ^
ECHO         If ^^^($msg.Length -gt 0^^^) { & ^
ECHO           $disp='Server got :'+$msg; & ^
ECHO           Write-Host^^^($disp^^^); & ^
ECHO           $curdte = Get-Date -Format 'HH:mm:ss'; & ^
ECHO           $reply='Reply to '+$msg+', im here, time is '+$curdte; & ^
ECHO           $reply_flag=0; & ^
ECHO           While^^^($reply_flag -eq 0^^^) { & ^
ECHO             Try { & ^
ECHO               Write-Output^^^($reply^^^) ^^^>5; & ^
ECHO               $reply_flag=1; & ^
ECHO             } Catch [System.IO.IOException] { & ^
ECHO               # Deal as you whish with potential errors & ^
ECHO             }; & ^
ECHO           }; & ^
ECHO         } & ^
ECHO       } Finally { & ^
ECHO         $npipeServer.Disconnect^^^(^^^); & ^
ECHO         $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO       } & ^
ECHO     }; & ^
ECHO   } Catch [System.IO.IOException] {  & ^
ECHO     # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty & ^
ECHO   } & ^
ECHO } Finally { & ^
ECHO   If ^^^($npipeServer^^^) { & ^
ECHO     $npipeServer.Dispose^^^(^^^); & ^
ECHO   } & ^
ECHO   # We close the fd & ^
ECHO   $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO } & ^
ECHO exit & ^
ECHO[)| START /B powershell 2>NUL >NUL
  SET /A LOCALV_TRY=20 >NUL
  :LaunchPowerShellSubProcessPipe_WaitForPipe
  powershell -nop -c "& {sleep -m 50}"
  SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
  IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO[|SET /P=|MORE 1>\.pipe!LOCALV_NAME!" 2>NUL || GOTO:LaunchPowerShellSubProcessPipe_WaitForPipe
  IF "!LOCALV_TRY!" == "0" EXIT /B 1
  REM 250 ms. PowerShell isn't the fastest thing to start when dealing with fd redirection... And we need it fully up to write to pipe and read from fd 5.
  powershell -nop -c "& {sleep -m 250}"
  EXIT /B 0

实现示例:

@echo off
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 (
  ECHO Can't use extensions
  EXIT /B 1
)
::
SETLOCAL ENABLEDELAYEDEXPANSION
IF ERRORLEVEL 1 (
  ECHO Can't use expansion 
  EXIT /B 1
)
REM We create 'MyNamedPipe'
CALL:LaunchPowerShellSubProcessPipe "MyNamedPipe"
SET "LOCALV_RET=!ERRORLEVEL!"
IF NOT "!LOCALV_RET!" == "0" (
  ECHO Failed to create the named pipe... Exit code from LaunchPowerShellSubProcessPipe : !LOCALV_RET!
  EXIT /B 1
)
REM Sending something through the pipe to the PowerShell subprocess that can be used as an IPC gate for other processes
ECHO Batch send hi to the PowerShell subprocess
ECHO Hi from the batch>\.pipeMyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
REM At this time, the PowerShell subprocess will take few cycles to write on the console and then write the response through fd 5.
REM We can check the pipe availability in the same way it's done in :LaunchPowerShellSubProcessPipe, but a direct read will suffice here. 
REM If we've read from the pipe, it should have ben a blocked read as we've created a synchronous pipe. 
REM As we read reply from fd 5, it's not synchronous, so we need to wait until we get the reply.
:WaitForReply
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
  ECHO Batch got a reply from SubProcess : %%R
  SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply
ECHO caller is happy >\.pipeMyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
:WaitForReply2
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
  ECHO Batch got a 2nd reply from SubProcess : %%R
  SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply2
REM Waiting one second for visible time delta
powershell -nop -c "& {sleep -m 1000}" 
ECHO This is my leave. >\.pipeMyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
:WaitForReply3
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
  ECHO Batch got a 3th reply from SubProcess : %%R
  SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply3
REM We can now tell goodbye to the PowerShell subprocess
ECHO QUIT>\.pipeMyNamedPipe
EXIT /B 0
::
:: Launch a PowerShell child process in the background linked to the console and 
:: earing through named pipe which name provided as an argument of this function.
::
:: Parameters :
::   [ Name] : A name for the named pipe.
:: Return :
::   0 if the child PowerShell has been successfully launched and the named pipe is available.
::   1 if it fails.
::   3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcessPipe
  SET LOCALV_NAME=
  IF NOT "%~1" == "" SET "LOCALV_NAME=%~1"
  IF "%~1" == "" ( ECHO :LaunchPowerShellSubProcessPipe need a name.& EXIT /B 1)
  powershell -command "$_" 2>&1 >NUL
  IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
  REM As properly stated by WReach [https://mathematica.stackexchange.com/questions/198187/how-to-read-a-named-pipe-on-windows], Windows named pipe have their own hand shake logic (WaitNamedPipe... {humor}Microsoft hate us all probably{/humor}
  REM To "read" the named pipe from batch in a POSIX way, we redirected reply to fd 5. Reply to the output part of the named pipe can't be read from batch directly nor with common windows tools. 
  REM Do not use [System.IO.Pipes.PipeOptions]::Asynchronous ! 
  (ECHO $CloseHandle = Add-Type 'A' -PassThru -MemberDefinition '[DllImport^^^("kernel32.dll"^^^)] public static extern bool CloseHandle^^^(IntPtr hObject^^^);'; & ^
ECHO # In case of, we close the fd beforehand & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream^^^('%LOCALV_NAME%', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256^^^); & ^
ECHO Try { & ^
ECHO   $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO   $pipeReader = new-object System.IO.StreamReader^^^($npipeServer^^^); & ^
ECHO   Try { & ^
ECHO     While^^^(^^^($msg = $pipeReader.ReadLine^^^(^^^)^^^) -notmatch 'QUIT'^^^) { & ^
ECHO       Try { & ^
ECHO         If ^^^($msg.Length -gt 0^^^) { & ^
ECHO           $disp='Server got :'+$msg; & ^
ECHO           Write-Host^^^($disp^^^); & ^
ECHO           $curdte = Get-Date -Format 'HH:mm:ss'; & ^
ECHO           $reply='Reply to '+$msg+', im here, time is '+$curdte; & ^
ECHO           $reply_flag=0; & ^
ECHO           While^^^($reply_flag -eq 0^^^) { & ^
ECHO             Try { & ^
ECHO               Write-Output^^^($reply^^^) ^^^>5; & ^
ECHO               $reply_flag=1; & ^
ECHO             } Catch [System.IO.IOException] { & ^
ECHO               # Deal as you whish with potential errors & ^
ECHO             }; & ^
ECHO           }; & ^
ECHO         } & ^
ECHO       } Finally { & ^
ECHO         $npipeServer.Disconnect^^^(^^^); & ^
ECHO         $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO       } & ^
ECHO     }; & ^
ECHO   } Catch [System.IO.IOException] {  & ^
ECHO     # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty & ^
ECHO   } & ^
ECHO } Finally { & ^
ECHO   If ^^^($npipeServer^^^) { & ^
ECHO     $npipeServer.Dispose^^^(^^^); & ^
ECHO   } & ^
ECHO   # We close the fd & ^
ECHO   $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO } & ^
ECHO exit & ^
ECHO[)| START /B powershell 2>NUL >NUL
  SET /A LOCALV_TRY=20 >NUL
  :LaunchPowerShellSubProcessPipe_WaitForPipe
  powershell -nop -c "& {sleep -m 50}"
  SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
  IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO[|SET /P=|MORE 1>\.pipe!LOCALV_NAME!" 2>NUL || GOTO:LaunchPowerShellSubProcessPipe_WaitForPipe
  IF "!LOCALV_TRY!" == "0" EXIT /B 1
  REM 250 ms. PowerShell isn't the fastest thing to start when dealing with fd redirection... And we need it fully up to write to pipe and read from fd 5.
  powershell -nop -c "& {sleep -m 250}"
  EXIT /B 0

预期输出:

批量向PowerShell子流程发送hiBatch从SubProcess得到回复:回复批次的嗨,我在这里,时间是14:15:34Batch从SubProcess收到了第二个回复:回复来电者很高兴,我在这里,时间是14:15:34Batch收到SubProcess的第3个回复:回复这是我的假期,我在这里,时间是14:15:35

最新更新