4

我有一个启动子进程的应用程序。子进程从标准输入读取要操作的文件。对于某些操作,它需要一个输入文件,其中包含有关如何处理它所操作的文件的信息——我们称之为“控制文件”。控制文件的名称也是从标准输入读取的。父应用程序可以使用临时文件作为控制文件,但我宁愿避免使用真实的磁盘支持文件。

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

我怎样才能在 Windows 上实现类似的事情?可以使用 Windows 提供的奇怪的“命名管道”吗,也就是说,它们可以被通常的 C 库fread()函数读取吗?如果是,我使用什么文件名来访问它们?还是有比使用命名管道更好的方法?

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

4

2 回答 2

0

更新:

@Harry Johnston 指出我误读了您的问题-您不想修改子流程。在这种情况下,您可以尝试使用from调用CreateProcess并填写hStdInput成员STARTUPINFOHANDLECreateNamedPipe

上一个答案:

一般来说,Windows CRT(C 运行时,或libcUnix 中的“”)是一个奇怪的野兽:它是 C 标准库的一个非常“准系统”的垫片,有一些额外的东西,维护得不是很好,也没有暴露很多东西Windows 可以做什么。用 C 语言编写 Windows 软件最自然的方法是 Win32 API。那说:

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

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

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

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

还是有比使用命名管道更好的方法?

您可以在家庭中使用套接字AF_UNIX,尽管最终会非常相似...

于 2012-08-01T20:00:20.313 回答
0

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

这是一个使用快速着色批处理输出的示例:PowerShell as a subprocess through named pipe

编辑:将其设置为消息模式并使其对@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>\\.\pipe\MyNamedPipe
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 >\\.\pipe\MyNamedPipe
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. >\\.\pipe\MyNamedPipe
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>\\.\pipe\MyNamedPipe
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

预期输出:

批量发送 hi 到 PowerShell 子进程
Batch 得到了 SubProcess 的回复 : 回复 Hi from the batch,我在这里,时间是 14:15:34
Batch 收到 SubProcess 的第二个回复:回复调用者很高兴,我在这里,时间是 14:15:34
Batch 收到了来自 SubProcess 的第三个回复:回复 This is my leave。, 我在这里,时间是 14:15:35
于 2021-03-15T14:57:03.160 回答