Что я могу использовать вместо канала Unix для межпроцессного взаимодействия в Windows?

У меня есть приложение, которое запускает подпроцесс. Подпроцесс считывает файлы для работы со стандартного ввода. Для некоторых операций ему нужен входной файл, содержащий информацию о том, что делать с файлами, с которыми он работает — назовем его «контрольным файлом». Имя управляющего файла также считывается со стандартного ввода. Родительское приложение может использовать временный файл в качестве управляющего файла, но я бы предпочел избегать реального файла на диске.

В Linux это просто: я могу создать канал Unix, разветвить, закрыть соответствующие концы канала перед запуском подпроцесса и использовать /dev/fd/3 (или любой другой файловый дескриптор) в качестве имени управляющего файла, а затем записать управляющие данные. в канал в родительском приложении. В качестве альтернативы я мог бы использовать именованный канал в /tmp (или что-то еще).

Как я могу добиться аналогичного результата в Windows? Могут ли для этого использоваться странные «именованные каналы», которые предлагает Windows, то есть могут ли они быть прочитаны обычной функцией fread() библиотеки C? Если да, то какое имя файла мне использовать для доступа к ним? Или есть лучший способ, чем использование именованных каналов?

(Подпроцесс представляет собой exiftool утилиту командной строки, запускаемую в пакетный режим, поэтому я не контролирую его. Родительское приложение написано на Python.)


person Sven Marnach    schedule 01.08.2012    source источник
comment
Боюсь, вряд ли это сработает; вам, вероятно, нужен реальный файл. Если задействован дисковый ввод-вывод, для Windows доступно программное обеспечение RAMdisk.   -  person Harry Johnston    schedule 02.08.2012
comment
@HarryJohnston: Дисковый ввод-вывод не является большой проблемой. Файлы, с которыми работает exiftool, обычно являются файлами мультимедиа, поэтому операции ввода-вывода, связанные с записью и чтением управляющего файла, затмеваются операциями ввода-вывода, связанными с чтением и записью файлов мультимедиа. Концептуально, канал — это то, что я буду использовать в Linux, поэтому я надеялся, что в Windows будет эквивалентное решение. Если это не так, я просто выберу решение с временным файлом.   -  person Sven Marnach    schedule 02.08.2012


Ответы (2)


Обновление:

@ Гарри Джонстон указывает, что я неправильно понял ваш вопрос - вы не хотите изменять подпроцесс. В этом случае вы можете попробовать позвонить CreateProcess и заполните hStdInput член STARTUPINFO с HANDLE из CreateNamedPipe.

Предыдущий ответ:

В общем, Windows CRT (среда выполнения C или "libc" на языке Unix) — странный зверь: это очень "простая" прокладка для стандартной библиотеки C с добавлением нескольких дополнительных вещей, не очень хорошо поддерживаемая и не раскрывающая многое из того, что может сделать Windows. Наиболее естественным способом написания программного обеспечения Windows на C является Win32 API. Это сказало:

Could the strange "named pipes" Windows offers be used for this, that is, can they be read from by the usual C library fread() function?

Да, я считаю, что вы можете сделать это с помощью _open_osfhandle, где первым параметром может быть HANDLE. Это даст вам целое число со странной насмешкой Windows CRT над файловым дескриптором Unix. Затем вы можете получить FILE* с помощью _fdopen.

If yes, what file name do I use to access them?

Я полагаю, вы могли бы попытаться создать случайный, который не сталкивается. Может быть, добавьте к нему префикс имени вашего приложения и возьмите некоторую комбинацию идентификатора процесса и текущего времени? Это просто то, что я выбрасываю.

Or is there a better way than using named pipes?

Вы можете использовать сокеты в семействе AF_UNIX, хотя в конечном итоге это будет очень похоже...

person asveikau    schedule 01.08.2012
comment
Использование _open_osfhandle потребует от OP изменения подпроцесса, что, по его словам, он не может сделать. - person Harry Johnston; 02.08.2012
comment
@HarryJohnston Хм, спасибо, я, кажется, пропустил это. Я обновлю ответ. - person asveikau; 02.08.2012
comment
Спасибо за Ваш ответ. Я не совсем понимаю, как участник hStdInput помогает с моей проблемой. Обратите внимание, что мне нужны оба, обычный stdin и какой-то другой канал для управляющего файла для связи с подпроцессом. Подпроцесс использует функцию библиотеки C для операций с файлами, и мне нужно имя файла для передачи подпроцессу. - person Sven Marnach; 02.08.2012

Вы можете использовать PowerShell в качестве пакетного подпроцесса для создания именованного канала и использовать его для создания IPC между пакетом и другими пакетами/подсистемами в Windows.

Вот пример использования этого для быстрого окрашивания пакетного вывода: 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>\\.\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

Ожидаемый результат:

Batch send hi to the PowerShell subprocess
Batch got a reply from SubProcess : Reply to Hi from the batch, im here, time is 14:15:34
Batch got a 2nd reply from SubProcess : Reply to caller is happy , im here, time is 14:15:34
Batch got a 3th reply from SubProcess : Reply to This is my leave. , im here, time is 14:15:35
person Zilog80    schedule 15.03.2021