父进程为 64 位时 StdIN/StdOUT 管道的问题

问题描述

我创建了一个类来启动子进程,该子进程继承了标准输入/输出/错误的新管道。在 32 位中一切正常:我可以在子 StdIn 中写入并读取子 StdOut/Err 没有问题(子进程也可以读取新的 StdIn 管道并写入新的 stdOut/Err 管道)。

但是,如果我以 64 位编译父进程,子进程(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.

*** 当我不重定向输出管道时,我可以在新窗口中手动写入(用我的键盘)并且孩子接收该数据。所以标准输入不是重定向。

在所有情况下,父进程都没有错误

我尝试调整 SECURITY_ATTRIBUTES 的 SecurityDescriptor 但没有成功。我知道 SECURITY_ATTRIBUTES 结构在 64 位中有不同的大小,但我不确定这是否会成为问题以及如何管理。

你有什么建议吗?有问题吗?

谢谢

如果你想测试,我做了一个较小的项目,只有最少的。

父代码:

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",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,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,false);
            StreamWriter SW = new StreamWriter(fsIN,Console.InputEncoding);

            ProcessInformation ProcessInfo;
            if (!CreateProcess(PathProgram,@"",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 作为子进程,或者,如果您愿意,可以使用我的 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 位父级,你应该得到:

父输出

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

子输出:无

使用 cmd.exe,使用 64 位父级,您应该得到:

父输出

Child started

子输出:什么都没有,cmd.exe 将关闭

使用具有 32 位父级的 ConsoleEcho.exe,您应该得到:

父输出

Child started
Child output 1 : 2021-01-22 13:52:24 ConsoleEcho begin
Child output 2 : echo Result should be in child process StdOUT

子输出:无

使用具有 64 位父级的 ConsoleEcho.exe,您应该得到:

父输出

Child started

子输出

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 找到了解决方案。

如果它可以帮助某人,这是我的方法/错误:

这是我第一次使用 createProcess+CreatePipe,我从 pInvoke 定义开始。但是,我在重定向管道时遇到了一些问题。所以,我在互联网上举了一个“工作”的例子(有这个字节/IntPtr 定义错误)。这段代码以 32 位运行,并带有很多结构/API 定义。我没有将这些定义与 pinvoke 进行比较/挑战。

当我以 64 位重建我的项目时,我的访问被拒绝。我搜索了提示/解决方案,发现只有 64 位的 CreateProcess/CreatePipe 出现“相同”错误(访问被拒绝)。根据我的发现,他们的问题与 32/64 位不同的结构 SECURITY_ATTRIBUTES 有关。

我在 Internet 上找到的代码对 SECURITY_ATTRIBUTES 也有这个错误的定义。但是,即使在纠正之后,错误仍然存​​在。我花了几个小时试图修复 SECURITY_ATTRIBUTES 或任何与之相关的问题。

所以,我部分地得到了答案(错误的定义),但我没有看对地方。我应该退后一步。

错误的 SECURITY_ATTRIBUTES 定义(32/64 位)可能会导致使用管道拒绝访问。 但是,在更改平台(32/64 位)时使用管道拒绝访问可能与错误的 SECURITY_ATTRIBUTES 定义无关...

解决方法

StartupInfo.reserved3 必须是 IntPtr。它是 MSDN 中的 LPBYTE lpReserved2

其他 pinvokes 似乎是正确的

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...