【问题标题】:redirect standard output for N level of child processes重定向 N 级子进程的标准输出
【发布时间】:2023-04-05 22:51:01
【问题描述】:

我在创建单个进程时成功地重定向了输出。但是,当该进程产生其他进程时,不会重定向任何输出。除了创建初始进程之外,我无法控制子进程的实例化方式/时间。似乎唯一的其他选择可能是 Win32 API 调用,但我没有发现这种方法的证据。

【问题讨论】:

    标签: c# winapi console-application redirectstandardoutput


    【解决方案1】:

    当我们无法控制生成子进程时,恐怕没有简单的方法可以实现这一点。这里想到了两种方法:

    1-Man in the middle:我们将子可执行文件替换为我们自己的 IO 重定向器可执行文件,然后它会启动真正的子进程并将其 IO 重定向到我们自己的。

    2-挂钩 CreateProcess 或 Std: 我们可以拦截进程创建并强制重定向所有中间进程,也可以拦截 Std 调用并将其报告给 root。

    注意:请注意,这些方法都不能 100% 有效,它们非常依赖于父子可执行行为。

    我选择 Man in the Middle,因为它更容易实施且不那么脆弱。

    假设我们有 3 个可执行文件:

    PA:我们的根可执行文件,我们可以完全控制代码和行为。

    PB:我们的直接子可执行文件,我们可以轻松地将 IO 重定向到我们自己的进程,但我们对代码和行为没有任何控制权。

    PC:PB 产生间接子代,我们无法控制它,也无法正常重定向 IO,因为它是由 PB 进程。

    我们可以在这里做的是引入另一个进程 PI,它将是 PC 的直接父进程,并将其 IO 重定向到自身,然后将其发送到我们的根进程(示例中为 PA

    这是我们 4 个可执行文件的代码

    为了实现这一点,我们有 4 个可执行文件(PA、PB、PC 和 PI)和一个库,其中包含用于进程重定向的共享代码以及 PAPI 之间的通信通道我在示例中使用了NetMQ,因为它易于使用且可靠但可以替换为类似的渠道,我们通过Nuget PM> Install-Package NetMQ 获取包。

    这是我们的类库的代码,PAPI 都引用了它:

    public class RedirectProcess
    {
        public static Process StartRedirected(string filename, string args = "")
        {
            var process = Process.Start(new ProcessStartInfo
            {
                Arguments = args,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                FileName = filename
            });
    
            process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
            process.BeginOutputReadLine();
    
            return process;
        }
    
        public static Process StartGrab(string filename, Action<string> dataHandle, string args = "")
        {
            var process = Process.Start(new ProcessStartInfo
            {
                Arguments = args,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                FileName = filename
            });
    
            process.OutputDataReceived += (s, e) => dataHandle(e.Data);
            process.BeginOutputReadLine();
    
            return process;
        }
    }
    
    
    public class RedirectClient:IDisposable
    {
        private readonly NetMQContext _context;
        private readonly RequestSocket _socket;
    
        public int Id { get; set; }
    
        public RedirectClient()
        {
            _context = NetMQContext.Create();
            _socket = _context.CreateRequestSocket();
            _socket.Connect("ipc://redirectCollector");
        }
    
        public void Send(string data)
        {
            _socket.Send(string.Format("{0},{1}", Id, data));
            _socket.Receive();//ack
        }
    
        public void Dispose()
        {
            _context.Dispose();
        }
    }
    
    
    public class RediretServer:IDisposable
    {
        private bool _start;
        public event EventHandler<ProcessDataArgs> ProcessDataReceived;
    
        public RediretServer()
        {
            _start = true;
    
           new Thread(() =>
           {
               using (var context = NetMQContext.Create())
               {
                   var socket = context.CreateResponseSocket();
                   socket.Bind("ipc://redirectCollector");
    
                   while (_start)
                   {
                       var res = socket.ReceiveString(TimeSpan.FromSeconds(1));
                       if (string.IsNullOrEmpty(res))
                           continue;
    
                       if (ProcessDataReceived != null)
                           ProcessDataReceived(this, new ProcessDataArgs(res));
    
                       socket.Send(new byte[]{ });//ack
                   }
               }
           }).Start();
        }
    
        public void Dispose()
        {
            _start = false;
        }
    }
    
    
    public class ProcessDataArgs:EventArgs
    {
        public string Data { get;private set; }
        public int Id { get;private set; }
    
        public ProcessDataArgs(string result)
        {
            var i = result.IndexOf(','); //first comma
    
            Id = int.Parse(result.Substring(0, i));
            Data=result.Substring(i+1);
        }
    }
    

    这是我们的可执行文件:

    PA: (我们的主要可执行文件,所有 IO 都应该重定向到它)

    internal class Program
    {
        private static void Main(string[] args)
        {
            var server = new RediretServer();
            server.ProcessDataReceived += (s, e) => Console.WriteLine("pid:{0}, data:{1}", e.Id, e.Data);
    
            RedirectProcess.StartGrab("PB.exe", Console.WriteLine);
    
            while (true)
            {
                Console.WriteLine("Process A, ID: {0},- Hello", Process.GetCurrentProcess().Id);
                Thread.Sleep(1000);
            }
        }
    }
    

    PB: (只是为了模拟情况,我们的直接孩子我们可以轻松地将其 IO 重定向到我们自己的)

    class Program
    {
        static void Main(string[] args)
        {
    
            var process = Process.Start(new ProcessStartInfo
            {
                Arguments = "-argument",
                FileName = "PC.exe"
            });
    
            while (true)
            {
                Console.WriteLine("Process B, ID: {0},- Hello", Process.GetCurrentProcess().Id);
                Thread.Sleep(1000);
            }
        }
    }
    

    PC: (来自 PB 的目标嵌套子对象,我们要重定向其 IO)

    class Program
    {
        static void Main(string[] args)
        {
            var a = args.Length == 0 ? "default" : args[0];
    
            while (true)
            {
                Console.WriteLine("Process C, ID: {0}, Args: {1}- Hello", Process.GetCurrentProcess().Id, a);
                Thread.Sleep(1000);
            }
        }
    }
    

    PI: (我们应该用 PC 替换的重定向器可执行文件)

    class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("**Process Redirector for PC**");
    
            var client = new RedirectClient();
    
            var interceptArgs = args
                .Aggregate(string.Empty, (current, a) => current + (string.Format("\"{0}\"", a)));
    
            var process=RedirectProcess.StartGrab("PC-real.exe", s =>
            {
                Console.WriteLine(s);
                client.Send(s);
            },
                interceptArgs);
    
            client.Id = process.Id;
            process.WaitForExit();
    
            client.Dispose();
        }
    }
    

    }

    现在让我们构建它并将所有可执行文件放在一个文件夹中,如果我们运行 PA 我们应该会看到两个控制台弹出,PA 但不是 PC 控制台输出应该是这样的:

    PA 输出: (连续流)

    进程 A,ID:85568,- 你好

    进程 B,ID:85640,- 你好

    PC 输出: (连续流)

    进程 C,ID:87012,参数:-argument- Hello

    不要将 PC.exe 重命名为 PC-Real.exe 并将拦截器 PI.exe 重命名为 PC.exe。

    如果我们再次运行PA,会弹出拦截器而不是原来的PC,我们将把它的IO从PA进程中取出。 p>

    进程 A,ID:85144,- 你好

    进程 B,ID:86436,- 你好

    pid:83660,数据:进程 C,ID:83660,参数:-argument- Hello

    【讨论】:

    • 我无权访问生成任何孙进程的代码。我所能控制的是如何启动初始可执行文件。
    • 恐怕没有简单的方法,我想到了2种方法,每种方法都适合特定情况,中间1人,你可以创建一个类似的拦截器到答案中的一个并将其作为原始可执行文件(子一个),然后这将执行类似于单个进程的重定向并将其传递给根,或者直接使用管道、ipc 通道或..将其直接发送给根。 . 2- 使用 EasyHook 等挂钩库挂钩 StdHandleCreateProcess Win32 API,如果您确认其中一个有用,我可以相应地更新我的答案
    • 其中任何一种方法听起来都有希望。期待您的更新答案
    • @MitchA,更新了中间人解决方案的答案,希望对您有所帮助。
    • 我可能不在这里,但看起来这个解决方案只有在我知道我不知道的所有子可执行文件编译时间的名称/位置时才有效。我不知道会产生多少子进程,更不用说它们的名字了。
    猜你喜欢
    • 2013-06-02
    • 2011-01-10
    • 2022-12-14
    • 1970-01-01
    • 2013-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-29
    相关资源
    最近更新 更多