【发布时间】:2023-04-05 22:51:01
【问题描述】:
我在创建单个进程时成功地重定向了输出。但是,当该进程产生其他进程时,不会重定向任何输出。除了创建初始进程之外,我无法控制子进程的实例化方式/时间。似乎唯一的其他选择可能是 Win32 API 调用,但我没有发现这种方法的证据。
【问题讨论】:
标签: c# winapi console-application redirectstandardoutput
我在创建单个进程时成功地重定向了输出。但是,当该进程产生其他进程时,不会重定向任何输出。除了创建初始进程之外,我无法控制子进程的实例化方式/时间。似乎唯一的其他选择可能是 Win32 API 调用,但我没有发现这种方法的证据。
【问题讨论】:
标签: c# winapi console-application redirectstandardoutput
当我们无法控制生成子进程时,恐怕没有简单的方法可以实现这一点。这里想到了两种方法:
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)和一个库,其中包含用于进程重定向的共享代码以及 PA 和 PI 之间的通信通道我在示例中使用了NetMQ,因为它易于使用且可靠但可以替换为类似的渠道,我们通过Nuget PM> Install-Package NetMQ 获取包。
这是我们的类库的代码,PA 和 PI 都引用了它:
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
【讨论】:
StdHandle 或 CreateProcess Win32 API,如果您确认其中一个有用,我可以相应地更新我的答案