当 parent 进程为 64 位时 StdIN/StdOUT 管道出现问题
Problem with StdIN/StdOUT pipes when parent process is 64bits
我创建了一个 class 来启动 child 继承标准 input/output/error 的新管道的进程。在 32 位中一切正常:我可以写入 child StdIn 并读取 child StdOut/Err 没有问题(child 进程也可以读取新的 StdIn 管道并写入新 stdOut/Err 管道)。
但是,如果我在 64 位中编译我的 parent 进程,child 进程(32 位和 64 位)无法读取新管道。
Parent | Child | RedirectPipes | Result (In Child process)
32bits | 32/64 | In+Out | GOOD
64bits | 32/64 | In+Out | Access Denied for StdIn (Console.ReadLine)
64bits | 32/64 | In | *** No error but no data for StdIn.
*** 当我不重定向输出管道时,我可以(用我的键盘)在新的 window 中手动写入并且 child 接收该数据。所以 stdIn 没有重定向。
在所有情况下,parent 过程中没有错误
我尝试调整 SECURITY_ATTRIBUTES 的 SecurityDescriptor 但没有成功。我知道 SECURITY_ATTRIBUTES 结构在 64 位中有不同的大小,但我不确定它是否会成为问题以及如何管理它。
你有什么建议吗?有问题吗?
谢谢
如果你想测试,我只做了一个最小的项目。
Parent代码:
using System;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
namespace Shell.TestShell
{
class TestShell
{
static void Main()
{
var OneShell = new Shell2();
}
}
class Shell2
{
public const Int32 STARTF_USESTDHANDLES = 0x100;
public const Int32 STARTF_USESHOWWINDOW = 1;
public const UInt16 SW_SHOW = 5;
public const UInt16 SW_HIDE = 0;
[Flags()]
public enum CreateProcessFlags
{
CREATE_SUSPENDED = 0x4,
DETACHED_PROCESS = 0x8,
CREATE_DEFAULT_ERROR_MODE = 0x4000000,
CREATE_NEW_CONSOLE = 0x10,
CREATE_NEW_PROCESS_GROUP = 0x200,
CREATE_NO_WINDOW = 0x8000000,
CREATE_SEPARATE_WOW_VDM = 0x800,
CREATE_UNICODE_ENVIRONMENT = 0x400,
IDLE_PRIORITY_CLASS = 0x40,
BELOW_NORMAL_PRIORITY_CLASS = 0x4000,
ABOVE_NORMAL_PRIORITY_CLASS = 0x8000,
NORMAL_PRIORITY_CLASS = 0x20,
HIGH_PRIORITY_CLASS = 0x80,
REALTIME_PRIORITY_CLASS = 0x100
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct StartupInfo
{
public int cb;
public String reserved;
public String desktop;
public String title;
public int x;
public int y;
public int xSize;
public int ySize;
public int xCountChars;
public int yCountChars;
public int fillAttribute;
public int flags;
public UInt16 showWindow;
public UInt16 reserved2;
public byte reserved3;
public SafeFileHandle hStdInput;
public SafeFileHandle hStdOutput;
public SafeFileHandle hStdError;
}
public struct ProcessInformation
{
public IntPtr process;
public IntPtr thread;
public int processId;
public int threadId;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcess(string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
CreateProcessFlags dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref StartupInfo lpStartupInfo,
out ProcessInformation lpProcessInformation);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr SecurityDescriptor;
public bool InheritHandle;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);
public Shell2()
{
StartupInfo Shell2StartupInfo = new StartupInfo();
Shell2StartupInfo.flags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
Shell2StartupInfo.showWindow = SW_SHOW; // SW_SHOW for testing only
Shell2StartupInfo.reserved = null;
Shell2StartupInfo.cb = Marshal.SizeOf(Shell2StartupInfo);
SECURITY_ATTRIBUTES lpPipeAttributesInput = new SECURITY_ATTRIBUTES();
lpPipeAttributesInput.InheritHandle = true;
lpPipeAttributesInput.Length = Marshal.SizeOf(lpPipeAttributesInput);
lpPipeAttributesInput.SecurityDescriptor = IntPtr.Zero;
// Parent pipes
SafeFileHandle StandardInputWriteHandle;
SafeFileHandle StandardOutputReadHandle;
// Child pipes
SafeFileHandle StandardInputReadHandle;
SafeFileHandle StandardOutputWriteHandle;
// New pipes for StdIN
if (!CreatePipe(out StandardInputReadHandle, out StandardInputWriteHandle, ref lpPipeAttributesInput, 0))
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
// New pipes for StdOUT
if (!CreatePipe(out StandardOutputReadHandle, out StandardOutputWriteHandle, ref lpPipeAttributesInput, 0))
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
// Redirect child pipes
Shell2StartupInfo.hStdInput = StandardInputReadHandle;
Shell2StartupInfo.hStdOutput = StandardOutputWriteHandle;
//Shell2StartupInfo.hStdOutput = new SafeFileHandle(IntPtr.Zero, false);
Shell2StartupInfo.hStdError = new SafeFileHandle(IntPtr.Zero, false);
String PathProgram;
// My testing child .Net Application
//PathProgram = @"C:\Temp\ConsoleEcho32.exe";
//PathProgram = @"C:\Temp\ConsoleEcho64.exe";
// cmd.exe same platform (32/64bits) as parent process
PathProgram = @"C:\Windows\System32\cmd.exe";
// Force 32 bits cmd.exe child from 64bits parent
//PathProgram = @"C:\Windows\SysWOW64\cmd.exe";
// Force 64 bits cmd.exe child from 32bits parent
//PathProgram = @"C:\Windows\sysnative\cmd.exe";
FileStream fsOUT = new FileStream(StandardOutputReadHandle, FileAccess.Read, 4096, false);
StreamReader SR = new StreamReader(fsOUT, Console.OutputEncoding);
FileStream fsIN = new FileStream(StandardInputWriteHandle, FileAccess.Write, 4096, false);
StreamWriter SW = new StreamWriter(fsIN, Console.InputEncoding);
ProcessInformation ProcessInfo;
if (!CreateProcess(PathProgram, @"",
IntPtr.Zero,
IntPtr.Zero,
true,
CreateProcessFlags.CREATE_NEW_CONSOLE, // CREATE_NEW_CONSOLE for testing only
IntPtr.Zero, @"C:\temp",
ref Shell2StartupInfo,
out ProcessInfo))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
Console.WriteLine("Child started");
SW.WriteLine("echo Result should be in child process StdOUT");
//SW.WriteLine(@"echo b > c:\temp\ttt.txt"); // test StdIN without StdOutput
SW.Flush();
for (int i = 1; i <= 5; i++)
{
Console.WriteLine("Child output " + i + " : "+ SR.ReadLine());
}
SW.Close();
Console.WriteLine("END");
Console.ReadLine();
}
}
}
您可以使用 cmd.exe 作为 child 进程,或者,如果您愿意,我的 ConsoleEcho 代码
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace ConsoleEcho
{
class Program
{
static void Main(string[] args)
{
String NewLine = Environment.NewLine;
String PathLog = @"c:\temp\logConsoleEcho.txt";
try
{
File.Delete(PathLog);
File.AppendAllText(PathLog, DateTime.Now.ToString() + " ConsoleEcho begin" + NewLine);
Console.WriteLine(DateTime.Now.ToString() + " ConsoleEcho begin");
File.AppendAllText(PathLog, "After first WriteLine" + NewLine); // Useful if the process crash of freeze while writing to StdOut
String Input;
do
{
Input = Console.ReadLine();
File.AppendAllText(PathLog, Input + NewLine);
Console.WriteLine(Input);
} while (Input != null);
}
catch (Exception Ex)
{
int error = Marshal.GetLastWin32Error();
File.AppendAllText(PathLog, "The last Win32 Error was: " + error + NewLine);
Console.WriteLine("The last Win32 Error was: " + error);
File.AppendAllText(PathLog, Ex.ToString() + NewLine);
Console.WriteLine(Ex.ToString());
File.AppendAllText(PathLog, Ex.HResult.ToString() + NewLine);
Console.WriteLine(Ex.HResult.ToString());
System.Threading.Thread.Sleep(30000);
}
}
}
}
使用cmd.exe,32位parent,你应该得到:
Parent输出
Child started
Child output 1 : Microsoft Windows [Version 10.0.18363.1198]
Child output 2 : (c) 2019 Microsoft Corporation. All rights reserved.
Child output 3 :
Child output 4 : C:\temp>echo Result should be in child process StdOUT
Child output 5 : Result should be in child process StdOUT
END
Child 输出:无
使用 cmd.exe,64 位 parent,你应该得到:
Parent输出
Child started
Child 输出:无,cmd.exe 将关闭
使用ConsoleEcho.exe,32位parent,你应该得到:
Parent输出
Child started
Child output 1 : 2021-01-22 13:52:24 ConsoleEcho begin
Child output 2 : echo Result should be in child process StdOUT
Child 输出:无
使用 ConsoleEcho.exe,使用 64 位 parent,你应该得到:
Parent输出
Child started
Child输出
2021-01-22 13:55:15 ConsoleEcho begin
The last Win32 Error was: 5
System.UnauthorizedAccessException: Access to the path is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.__ConsoleStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.StreamReader.ReadBuffer()
at System.IO.StreamReader.ReadLine()
at System.IO.TextReader.SyncTextReader.ReadLine()
at System.Console.ReadLine()
at ConsoleEcho.Program.Main(String[] args) in \..\ConsoleEcho\Program.cs:line 25
-2147024891
。
.
.
Xanatos 找到了解决方案。
这是我的 approach/mistake 如果它可以帮助某人:
我是第一次使用 createProcess+CreatePipe,我从 pInvoke 定义开始。但是,我在管道重定向方面遇到了一些问题。所以,我在 Internet 上举了一个“有效”的例子(有这个 byte/IntPtr 定义错误)。此代码在 32 位中运行,并带有许多 struct/API 定义。我没有 compared/challenged 这些针对 pinvoke 的定义。
当我在 64 位中重建我的项目时,访问被拒绝。我搜索 hints/solutions 并仅在 64 位中发现 CreateProcess/CreatePipe 的“相同”错误(访问被拒绝)。根据我的发现,他们的问题与 struct SECURITY_ATTRIBUTES 有关,该结构在 32/64 位中不同。
我在 Internet 上找到的代码对 SECURITY_ATTRIBUTES 也有这个错误的定义。但是,即使在更正之后,错误仍然存在。我花了几个小时试图修复 SECURITY_ATTRIBUTES 或任何与之相关的东西。
所以,我得到了部分答案(错误的定义),但我没有找到正确的地方。我应该退一步。
SECURITY_ATTRIBUTES 的错误定义(32/64 位)可能会导致管道拒绝访问。
但是,在更改平台(32/64 位)时使用管道拒绝访问可能与错误的 SECURITY_ATTRIBUTES 定义无关...
StartupInfo.reserved3
必须是 IntPtr
。它是 LPBYTE lpReserved2
中的 MSDN.
其他pinvokes似乎是正确的
我创建了一个 class 来启动 child 继承标准 input/output/error 的新管道的进程。在 32 位中一切正常:我可以写入 child StdIn 并读取 child StdOut/Err 没有问题(child 进程也可以读取新的 StdIn 管道并写入新 stdOut/Err 管道)。
但是,如果我在 64 位中编译我的 parent 进程,child 进程(32 位和 64 位)无法读取新管道。
Parent | Child | RedirectPipes | Result (In Child process)
32bits | 32/64 | In+Out | GOOD
64bits | 32/64 | In+Out | Access Denied for StdIn (Console.ReadLine)
64bits | 32/64 | In | *** No error but no data for StdIn.
*** 当我不重定向输出管道时,我可以(用我的键盘)在新的 window 中手动写入并且 child 接收该数据。所以 stdIn 没有重定向。
在所有情况下,parent 过程中没有错误
我尝试调整 SECURITY_ATTRIBUTES 的 SecurityDescriptor 但没有成功。我知道 SECURITY_ATTRIBUTES 结构在 64 位中有不同的大小,但我不确定它是否会成为问题以及如何管理它。
你有什么建议吗?有问题吗?
谢谢
如果你想测试,我只做了一个最小的项目。
Parent代码:
using System;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
namespace Shell.TestShell
{
class TestShell
{
static void Main()
{
var OneShell = new Shell2();
}
}
class Shell2
{
public const Int32 STARTF_USESTDHANDLES = 0x100;
public const Int32 STARTF_USESHOWWINDOW = 1;
public const UInt16 SW_SHOW = 5;
public const UInt16 SW_HIDE = 0;
[Flags()]
public enum CreateProcessFlags
{
CREATE_SUSPENDED = 0x4,
DETACHED_PROCESS = 0x8,
CREATE_DEFAULT_ERROR_MODE = 0x4000000,
CREATE_NEW_CONSOLE = 0x10,
CREATE_NEW_PROCESS_GROUP = 0x200,
CREATE_NO_WINDOW = 0x8000000,
CREATE_SEPARATE_WOW_VDM = 0x800,
CREATE_UNICODE_ENVIRONMENT = 0x400,
IDLE_PRIORITY_CLASS = 0x40,
BELOW_NORMAL_PRIORITY_CLASS = 0x4000,
ABOVE_NORMAL_PRIORITY_CLASS = 0x8000,
NORMAL_PRIORITY_CLASS = 0x20,
HIGH_PRIORITY_CLASS = 0x80,
REALTIME_PRIORITY_CLASS = 0x100
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct StartupInfo
{
public int cb;
public String reserved;
public String desktop;
public String title;
public int x;
public int y;
public int xSize;
public int ySize;
public int xCountChars;
public int yCountChars;
public int fillAttribute;
public int flags;
public UInt16 showWindow;
public UInt16 reserved2;
public byte reserved3;
public SafeFileHandle hStdInput;
public SafeFileHandle hStdOutput;
public SafeFileHandle hStdError;
}
public struct ProcessInformation
{
public IntPtr process;
public IntPtr thread;
public int processId;
public int threadId;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcess(string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
CreateProcessFlags dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref StartupInfo lpStartupInfo,
out ProcessInformation lpProcessInformation);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr SecurityDescriptor;
public bool InheritHandle;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);
public Shell2()
{
StartupInfo Shell2StartupInfo = new StartupInfo();
Shell2StartupInfo.flags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
Shell2StartupInfo.showWindow = SW_SHOW; // SW_SHOW for testing only
Shell2StartupInfo.reserved = null;
Shell2StartupInfo.cb = Marshal.SizeOf(Shell2StartupInfo);
SECURITY_ATTRIBUTES lpPipeAttributesInput = new SECURITY_ATTRIBUTES();
lpPipeAttributesInput.InheritHandle = true;
lpPipeAttributesInput.Length = Marshal.SizeOf(lpPipeAttributesInput);
lpPipeAttributesInput.SecurityDescriptor = IntPtr.Zero;
// Parent pipes
SafeFileHandle StandardInputWriteHandle;
SafeFileHandle StandardOutputReadHandle;
// Child pipes
SafeFileHandle StandardInputReadHandle;
SafeFileHandle StandardOutputWriteHandle;
// New pipes for StdIN
if (!CreatePipe(out StandardInputReadHandle, out StandardInputWriteHandle, ref lpPipeAttributesInput, 0))
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
// New pipes for StdOUT
if (!CreatePipe(out StandardOutputReadHandle, out StandardOutputWriteHandle, ref lpPipeAttributesInput, 0))
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
// Redirect child pipes
Shell2StartupInfo.hStdInput = StandardInputReadHandle;
Shell2StartupInfo.hStdOutput = StandardOutputWriteHandle;
//Shell2StartupInfo.hStdOutput = new SafeFileHandle(IntPtr.Zero, false);
Shell2StartupInfo.hStdError = new SafeFileHandle(IntPtr.Zero, false);
String PathProgram;
// My testing child .Net Application
//PathProgram = @"C:\Temp\ConsoleEcho32.exe";
//PathProgram = @"C:\Temp\ConsoleEcho64.exe";
// cmd.exe same platform (32/64bits) as parent process
PathProgram = @"C:\Windows\System32\cmd.exe";
// Force 32 bits cmd.exe child from 64bits parent
//PathProgram = @"C:\Windows\SysWOW64\cmd.exe";
// Force 64 bits cmd.exe child from 32bits parent
//PathProgram = @"C:\Windows\sysnative\cmd.exe";
FileStream fsOUT = new FileStream(StandardOutputReadHandle, FileAccess.Read, 4096, false);
StreamReader SR = new StreamReader(fsOUT, Console.OutputEncoding);
FileStream fsIN = new FileStream(StandardInputWriteHandle, FileAccess.Write, 4096, false);
StreamWriter SW = new StreamWriter(fsIN, Console.InputEncoding);
ProcessInformation ProcessInfo;
if (!CreateProcess(PathProgram, @"",
IntPtr.Zero,
IntPtr.Zero,
true,
CreateProcessFlags.CREATE_NEW_CONSOLE, // CREATE_NEW_CONSOLE for testing only
IntPtr.Zero, @"C:\temp",
ref Shell2StartupInfo,
out ProcessInfo))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
Console.WriteLine("Child started");
SW.WriteLine("echo Result should be in child process StdOUT");
//SW.WriteLine(@"echo b > c:\temp\ttt.txt"); // test StdIN without StdOutput
SW.Flush();
for (int i = 1; i <= 5; i++)
{
Console.WriteLine("Child output " + i + " : "+ SR.ReadLine());
}
SW.Close();
Console.WriteLine("END");
Console.ReadLine();
}
}
}
您可以使用 cmd.exe 作为 child 进程,或者,如果您愿意,我的 ConsoleEcho 代码
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace ConsoleEcho
{
class Program
{
static void Main(string[] args)
{
String NewLine = Environment.NewLine;
String PathLog = @"c:\temp\logConsoleEcho.txt";
try
{
File.Delete(PathLog);
File.AppendAllText(PathLog, DateTime.Now.ToString() + " ConsoleEcho begin" + NewLine);
Console.WriteLine(DateTime.Now.ToString() + " ConsoleEcho begin");
File.AppendAllText(PathLog, "After first WriteLine" + NewLine); // Useful if the process crash of freeze while writing to StdOut
String Input;
do
{
Input = Console.ReadLine();
File.AppendAllText(PathLog, Input + NewLine);
Console.WriteLine(Input);
} while (Input != null);
}
catch (Exception Ex)
{
int error = Marshal.GetLastWin32Error();
File.AppendAllText(PathLog, "The last Win32 Error was: " + error + NewLine);
Console.WriteLine("The last Win32 Error was: " + error);
File.AppendAllText(PathLog, Ex.ToString() + NewLine);
Console.WriteLine(Ex.ToString());
File.AppendAllText(PathLog, Ex.HResult.ToString() + NewLine);
Console.WriteLine(Ex.HResult.ToString());
System.Threading.Thread.Sleep(30000);
}
}
}
}
使用cmd.exe,32位parent,你应该得到:
Parent输出
Child started
Child output 1 : Microsoft Windows [Version 10.0.18363.1198]
Child output 2 : (c) 2019 Microsoft Corporation. All rights reserved.
Child output 3 :
Child output 4 : C:\temp>echo Result should be in child process StdOUT
Child output 5 : Result should be in child process StdOUT
END
Child 输出:无
使用 cmd.exe,64 位 parent,你应该得到:
Parent输出
Child started
Child 输出:无,cmd.exe 将关闭
使用ConsoleEcho.exe,32位parent,你应该得到:
Parent输出
Child started
Child output 1 : 2021-01-22 13:52:24 ConsoleEcho begin
Child output 2 : echo Result should be in child process StdOUT
Child 输出:无
使用 ConsoleEcho.exe,使用 64 位 parent,你应该得到:
Parent输出
Child started
Child输出
2021-01-22 13:55:15 ConsoleEcho begin
The last Win32 Error was: 5
System.UnauthorizedAccessException: Access to the path is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.__ConsoleStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.StreamReader.ReadBuffer()
at System.IO.StreamReader.ReadLine()
at System.IO.TextReader.SyncTextReader.ReadLine()
at System.Console.ReadLine()
at ConsoleEcho.Program.Main(String[] args) in \..\ConsoleEcho\Program.cs:line 25
-2147024891
。 . .
Xanatos 找到了解决方案。
这是我的 approach/mistake 如果它可以帮助某人:
我是第一次使用 createProcess+CreatePipe,我从 pInvoke 定义开始。但是,我在管道重定向方面遇到了一些问题。所以,我在 Internet 上举了一个“有效”的例子(有这个 byte/IntPtr 定义错误)。此代码在 32 位中运行,并带有许多 struct/API 定义。我没有 compared/challenged 这些针对 pinvoke 的定义。
当我在 64 位中重建我的项目时,访问被拒绝。我搜索 hints/solutions 并仅在 64 位中发现 CreateProcess/CreatePipe 的“相同”错误(访问被拒绝)。根据我的发现,他们的问题与 struct SECURITY_ATTRIBUTES 有关,该结构在 32/64 位中不同。
我在 Internet 上找到的代码对 SECURITY_ATTRIBUTES 也有这个错误的定义。但是,即使在更正之后,错误仍然存在。我花了几个小时试图修复 SECURITY_ATTRIBUTES 或任何与之相关的东西。
所以,我得到了部分答案(错误的定义),但我没有找到正确的地方。我应该退一步。
SECURITY_ATTRIBUTES 的错误定义(32/64 位)可能会导致管道拒绝访问。 但是,在更改平台(32/64 位)时使用管道拒绝访问可能与错误的 SECURITY_ATTRIBUTES 定义无关...
StartupInfo.reserved3
必须是 IntPtr
。它是 LPBYTE lpReserved2
中的 MSDN.
其他pinvokes似乎是正确的