Thanks Dave for the link!
With it, I've been able to fully resolve the problem, and write a resilient :CreatePipe, that adapts to ANY previous combination of open handles.
(Note that I've improved upon your last script, as there are cases where you thought the redirected handle could not be identified, but in fact it can - I'll post a comment in the other thread about that.)
The following test script supports N optional arguments, specifying the handle numbers that should be reserved before attempting the pipe creations.
Passing the list of known used handles to :CreatePipe allows to optimize the use of remaining ones.
But even if there are unlisted ones used, it will still work in most cases: The only limitation then is that in some cases it will only find 3 of the 4 free handles left, and give up where success could have been possible.
Code: Select all
@echo off
:# Rerun self in a sub-shell, to avoid breaking the original shell file handles
echo %0 | findstr :: >nul || (cmd /d /c ^""%~dp0\::\..\%~nx0" %*^" & exit /b)
goto :main
:#----------------------------------------------------------------------------#
:# Handle enumeration routine - Return lists of used and free file I/O handles
:EnumHandles %1=Optional list of already allocated handles (Not including 0 1 2)
:# Returns %freeHandles%, %usedHandles%, %nUnknownHandles%
setlocal EnableExtensions EnableDelayedExpansion
%ECHO.D% call :EnumHandles "%~1"
set "freeHandles=" &:# List of free file handles
set "usedHandles=" &:# List of used file handles
set "nUnknownHandles=1" &:# 1=One of the used handles is actually free, but we don't know which one
2>nul ( :# Try redirecting handles 3 to 8, to see if they exist. 2>nul hides error messages if they don't.
for /L %%h in (3,1,8) do call :TryHandleRedir 9 %%h
)
:# If none of the handles 3 to 8 are free then there are three possibilities for handle 9
:# 1. It is occupied before us, then we MAY have one free handle which is occupied by redirection of 9
:# 2. It is occupied by redirection of stderr, then we have no free handles
:# 3. It is free, then have only one free handle which is handle 9
if defined freeHandles 2>nul call :TryHandleRedir 1 9
if defined freeHandles 2>nul (
set /a "highUsed=%usedHandles:~-1%"
if "!highUsed!"=="9" (
set /a "highFree=%freeHandles:~-1%"
set "freeHandles="
set "usedHandles="
for /L %%h in (3,1,!highFree!) do call :TryHandleRedir !highFree! %%h
set /a "nextUsed=!highFree!+1"
for /L %%h in (!nextUsed!,1,9) do (
set "usedHandles=!usedHandles! %%h"
)
)
)
:# If the first used handle is followed by the first free handle, then we know
:# it's the one that was used by the 2>NUL redirection. So it's actually free.
:# More generally, if all used handles before the first free one are known used
:# handles passed in %1, except for one, then that unknown one is actually free.
if defined freeHandles if defined usedHandles (
set "firstFreeHandle=%freeHandles:~1,1%"
%ECHO.D% firstFreeHandle=!firstFreeHandle!
set /a "nUnknownHandles=firstFreeHandle-3"
%ECHO.D% nUnknownHandles=!nUnknownHandles!
set "knownUsedHandles=%~1"
%ECHO.D% knownUsedHandles=!knownUsedHandles!
set "unknownHandles="
if not !firstFreeHandle!==3 (
set /a "lastUnknowHandle=firstFreeHandle-1"
for /l %%h in (3,1,!lastUnknowHandle!) do set "unknownHandles=!unknownHandles! %%h"
)
if defined knownUsedHandles for %%h in (!knownUsedHandles!) do (
set /a "DIF=%%h-firstFreeHandle" &:# If DIF < 0 then %%h < firstFreeHandle
%ECHO.D% DIF=!DIF!
if "!DIF:~0,1!"=="-" (
set /a "nUnknownHandles-=1"
set "unknownHandles=!unknownHandles: %%h=!"
)
)
%ECHO.D% unknownHandles=!unknownHandles!
if !nUnknownHandles!==1 ( :# OK, this single used handle is actually free.
set "nUnknownHandles=0"
set "freeHandles=!unknownHandles!%freeHandles%"
for %%h in ("!unknownHandles!") do set "usedHandles=!usedHandles:%%~h=!"
)
)
:# Cleanup and return
for %%v in (freeHandles usedHandles) do if defined %%v set "%%v=!%%v:~1!" &:# Remove the head space
endlocal & (
set "freeHandles=%freeHandles%"
set "usedHandles=%usedHandles%"
set "nUnknownHandles=%nUnknownHandles%"
) & (%ECHO.D% return) & exit /b
:TryHandleRedir %1=Handle to redirect; %2=Handle to duplicate; Returns 0=Used, 1=Free
break %1>&%2 && (
set "usedHandles=!usedHandles! %2"
(call,) &rem Clear ERRORLEVEL
) || (
set "freeHandles=!freeHandles! %2"
)
exit /b
:#----------------------------------------------------------------------------#
:# Pipe creation routine.
:CreatePipe %1=PipeIn name; %2=PipeOut name
call :EnumHandles "%knownPipeHandles%"
%ECHO.D% freeHandles=%freeHandles%
%ECHO.D% nUnknownHandles=%nUnknownHandles%
if "%freeHandles:~6,1%"=="" exit /b 1 &:# Not enough free handles. There must be 4 or more. Ex: "0 2 4 6"
set "PROTECT="
if not %nUnknownHandles%==0 set "PROTECT=2>NUL"
for /f "tokens=1,2,4" %%a in ("%freeHandles%") do (
set "%1=%%a" &:# PipeIn handle
set "%2=%%b" &:# PipeOut handle
set "knownPipeHandles=%knownPipeHandles% %%a %%b"
setlocal
set "hIn=%%a" &:# PipeIn handle
set "hOut=%%b" &:# PipeOut handle
set "hTmp=%%c" &rem Temporary handle, released after use
)
endlocal & %PROTECT% (rundll32 1>&%hOut% %hOut%>&%hTmp% | rundll32 0>&%hIn% %hIn%>&%hTmp%)
exit /b
:#----------------------------------------------------------------------------#
:main
setlocal EnableExtensions EnableDelayedExpansion
set "ECHO.D=if "%DEBUG%"=="1" echo"
set "ARGS=%*"
set "knownPipeHandles=%ARGS%" &:# List of known used handles, passed to :CreatePipe for optimizing results
set "HIDE_HANDLES="
if defined ARGS (
set "HIDE_HANDLES=%ARGS% "
set "HIDE_HANDLES=!HIDE_HANDLES: =>NUL !"
)
%HIDE_HANDLES% (
call :CreatePipe P1IN P1OUT &:# There are 3 handles in use before this call
if errorlevel 1 echo Error: Failed to find 4 free handles for pipe 1 & exit /b 1
echo P1IN=!P1IN! P1OUT=!P1OUT!
call :CreatePipe P2IN P2OUT &:# There are 5 handles in use before this call
if errorlevel 1 echo Error: Failed to find 4 free handles for pipe 2 & exit /b 1
echo P2IN=!P2IN! P2OUT=!P2OUT!
)
%ECHO.D% Starting I/Os to pipes
>&%P1OUT% echo From child 1 on pipe 1 &:# Write to pipe 1
>&%P2OUT% echo From child 2 on pipe 2 &:# Write to pipe 2
<&%P1IN% set /p "READ=" &:# Read value from pipe 1
echo Read on pipe 1: %READ%
<&%P2IN% set /p "READ=" &:# Read value from pipe 2
echo Read on pipe 2: %READ%
Set "DEBUG=1" to enable debug messages if you want to understand what's going on.
Code: Select all
C:\JFL\Temp>test12
P1IN=3 P1OUT=4
P2IN=5 P2OUT=6
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2
C:\JFL\Temp>test12 3
P1IN=4 P1OUT=5
P2IN=6 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2
C:\JFL\Temp>test12 4
P1IN=3 P1OUT=5
P2IN=6 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2
C:\JFL\Temp>test12 5
P1IN=3 P1OUT=4
P2IN=6 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2
C:\JFL\Temp>test12 6
P1IN=3 P1OUT=4
P2IN=5 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2
C:\JFL\Temp>test12 7
P1IN=3 P1OUT=4
P2IN=5 P2OUT=6
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2
C:\JFL\Temp>test12 3 5
P1IN=4 P1OUT=6
Error: Failed to find 4 free handles for pipe 2
C:\JFL\Temp>