获取句柄并写入启动我们过程的控制台

获取句柄并写入启动我们过程的控制台

问题描述:

如何写一些已经打开的控制台的标准输出?我用这段代码找到了我需要的控制台:

How could I write to the standard output of some already open console? I find the console I need with this piece of code:

    IntPtr ptr = GetForegroundWindow();           
    int u;
    GetWindowThreadProcessId(ptr, out u);
    Process process = Process.GetProcessById(u);

问题在于如何获取此过程的标准输出句柄指针(stdHandle).

The problem is how to get the standard output handle pointer (stdHandle) of this process.

然后我想要类似的东西:

I would then want something like:

                SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
                FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
                Encoding encoding = Encoding.ASCII;
                StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
                standardOutput.AutoFlush = true;
                Console.SetOut(standardOutput);

使用Windows API的C ++代码是可以的-我可以使用pInvoke.

Code in C++ using windows API is OK - I can use pInvoke.

有效地,我想将文本写到一个尚未打开的控制台窗口中,该窗口不是由我的进程生成的(这是通过命令行启动我的进程时位于前台的窗口-但是我的进程是WinApp,所以控制台不附加标准).

Effectively what I would like is to write text to an already open console window not spawned by my process (and it is the one that was in foreground when launching my process through command line - but my process is a WinApp so the console does not attach the std).

创建过程后,可以重定向标准输出吗?

Can the standard output be redirected after the process has been created?

PS:我读到一些可用于执行此操作的COM文件,因此这意味着有一种编程方式...

PS: I read about some COM file that can be used to do this, so this means that there is a programmatic way ...

谢谢!

我终于想出了如何在启动Windows应用程序时将其透明地附加到控制台(如果它是前景窗口)的情况.

I finally figured out how to attach transparently to a console if it is the foreground window while launching the windows app.

不要问我为什么必须传递STD_ERROR_HANDLE而不是STD_OUTPUT_HANDLE,但这只是可行,可能是因为可以共享标准错误.

Don't ask me why STD_ERROR_HANDLE must be passed instead of STD_OUTPUT_HANDLE, but it simply works, probably because the standard error can be shared.

N.B .:控制台在显示您内部的应用程序消息时可以接受用户输入,但是在stderr从您的应用程序输出时使用它有点令人困惑.

N.B.: the console can accept user input while displaying you app messages inside, but it is a bit confusing to use it while the stderr is outputting from you app.

如果使用此代码段,则从带有至少一个参数的控制台窗口启动应用程序时,它将附加Console.Write;如果使用参数/debug启动应用程序,则它将附加调试.写入控制台.

With this snippet of code if you launch you app from a console window with at least one parameter it will attach Console.Write to it, and if you launch the app with the parameter /debug then it will attach even the Debug.Write to the console.

在退出应用程序之前调用Cleanup()以释放控制台,并发送Enter键释放最后一行,以便控制台与启动应用程序之前一样可用.

Call Cleanup() before exiting you app to free the console and send an Enter keypress to release the last line so the console is usable as before starting the app.

PS.您无法通过此方法使用输出重定向,即:yourapp.exe> file.txt,因为您将得到一个空文件.甚至不要尝试myapp.exe> file.txt 2>& 1,因为这会使应用程序崩溃(将错误重定向到输出意味着我们正在尝试附加到非共享缓冲区).

PS. You cannto use output redirection with this method ie.: yourapp.exe > file.txt because you will get an empty file. And dont even try myapp.exe > file.txt 2>&1 because you will crash the app (redirecting error to output means we are trying to attach to a nonshared buffer).

这是代码:

[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

[DllImport("kernel32.dll",
    EntryPoint = "GetStdHandle",
    SetLastError = true,
    CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetStdHandle(int nStdHandle);

[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll",
    EntryPoint = "AllocConsole",
    SetLastError = true,
    CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
private static bool _consoleAttached = false;
private static IntPtr consoleWindow;

[STAThread]
static void Main()
{
    args = new List<string>(Environment.GetCommandLineArgs());

    int prId;
    consoleWindow = GetForegroundWindow();            
    GetWindowThreadProcessId(consoleWindow, out prId);
    Process process = Process.GetProcessById(prId);

    if (args.Count > 1 && process.ProcessName == "cmd")
    {
        if (AttachConsole((uint)prId)) {
            _consoleAttached = true;
            IntPtr stdHandle = GetStdHandle(STD_ERROR_HANDLE); // must be error dunno why
            SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
            FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
            Encoding encoding = Encoding.ASCII;
            StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
            standardOutput.AutoFlush = true;
            Console.SetOut(standardOutput);
            if (args.Contains("/debug")) Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
            Console.WriteLine(Application.ProductName + " was launched from a console window and will redirect output to it.");
        }
    }
    // ... do whatever, use console.writeline or debug.writeline
    // if you started the app with /debug from a console
    Cleanup();
}

private static void Cleanup() {
    try
    {
        if (_consoleAttached)
        {
            SetForegroundWindow(consoleWindow);
            SendKeys.SendWait("{ENTER}");
            FreeConsole();
        }    
    }        
}