Hice una clase para iniciar el proceso hijo que hereda nuevas tuberías para entrada / salida / error estándar. Todo funciona bien en 32 bits: puedo escribir en child StdIn y leer child StdOut / Err sin problema (el proceso hijo también puede leer la nueva tubería StdIn y escribir en las nuevas stdOut / Err pipe).

Pero, si compilo mi proceso principal en 64 bits, el proceso secundario (32 y 64 bits) no puede leer las nuevas tuberías.

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.

*** Cuando no redirijo la tubería de salida, puedo escribir manualmente (con mi teclado) en la nueva ventana y el niño recibe esos datos. Entonces, stdIn no es redireccionamiento.

En todo caso, no hay error en el proceso padre.

Intenté ajustar el SecurityDescriptor de SECURITY_ATTRIBUTES pero sin éxito. Sé que la estructura SECURITY_ATTRIBUTES tiene un tamaño diferente en 64 bits, pero no estoy seguro de si puede ser un problema y cómo administrarlo.

¿Tienes alguna sugerencia? ¿Preguntas?

Gracias

Si quieres probar, hice un proyecto más pequeño con solo lo mínimo.

Código padre:

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();
        }
    }
}

Puede usar cmd.exe como proceso hijo o, si lo prefiere, mi código 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);
            }
            
        }
    }
}

Con cmd.exe, con un elemento principal de 32 bits, debería obtener:

Salida principal

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

Salida del niño: Nada

Con cmd.exe, con un padre de 64 bits, debería obtener:

Salida principal

Child started

Salida secundaria: nada y el cmd.exe se cerrará

Con ConsoleEcho.exe, con un elemento principal de 32 bits, debería obtener:

Salida principal

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

Salida del niño: Nada

Con ConsoleEcho.exe, con un padre de 64 bits, debería obtener:

Salida principal

Child started

Salida secundaria

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

. . .

Xánatos encontró la solución.

Aquí está mi enfoque / error si puede ayudar a alguien:

Fue la primera vez para mí con createProcess + CreatePipe y comencé con la definición de pInvoke. Pero tuve algún problema con la redirección de tuberías. Entonces, tomo un ejemplo "funcional" en Internet (que tenía este error de definición de byte / IntPtr). Este código funcionaba en 32 bits y venía con muchas definiciones de estructura / API. No comparé / desafié estas definiciones con pinvoke.

Cuando reconstruí mi proyecto en 64 bits, me denegaron el acceso. Busqué sugerencias / soluciones y encontré el "mismo" error (acceso denegado) con CreateProcess / CreatePipe solo en 64 bits. Según lo que encontré, sus problemas estaban relacionados con la estructura SECURITY_ATTRIBUTES que es diferente en 32 / 64bits.

El código que encontré en Internet también tenía esta mala definición para SECURITY_ATTRIBUTES. Pero, incluso después de corregirlo, el error seguía ahí. Pasé varias horas tratando de arreglar SECURITY_ATTRIBUTES o cualquier cosa relacionada con él.

Entonces, tenía parcialmente la respuesta (definición incorrecta) pero no estaba mirando el lugar correcto. Debería haber dado un paso atrás.

Una mala definición (32/64 bits) de SECURITY_ATTRIBUTES puede resultar en un acceso denegado con pipe. Pero, un acceso denegado con tubería al cambiar de plataforma (32/64 bits) puede no estar relacionado con una mala definición de SECURITY_ATTRIBUTES ...

1
Aranea 22 ene. 2021 a las 22:22

1 respuesta

La mejor respuesta

StartupInfo.reserved3 debe ser un IntPtr. Es un LPBYTE lpReserved2 en MSDN.

Los otros pinvokes parecen ser correctos.

0
xanatos 26 ene. 2021 a las 15:19