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

时间:2011-04-19 03:27:35

标签: c# c++ windows winapi

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

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

问题是如何获得此进程的标准输出句柄指针(stdHandle)。

我想要的是:

                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。

实际上我想要的是将文本写入已经打开的控制台窗口而不是由我的进程生成(并且它是通过命令行启动我的进程时的前景 - 但我的进程是WinApp所以控制台不附加标准。)

创建过程后是否可以重定向标准输出?

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

谢谢!

4 个答案:

答案 0 :(得分:3)

如果在启动Windows应用程序时它是前台窗口,我终于想出了如何透明地连接到控制台。

不要问我为什么必须传递STD_ERROR_HANDLE而不是STD_OUTPUT_HANDLE,但它只是起作用,可能是因为可以共享标准错误。

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

如果您从控制台窗口启动应用程序,并且至少有一个参数,则会使用此代码片段将Console.Write附加到该应用程序,如果您使用参数/ debug启动应用程序,则它甚至会附加调试。写入控制台。

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

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

以下是代码:

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

答案 1 :(得分:1)

如果您只想写一些其他应用程序使用的控制台,那么您可以使用以下内容 - 您需要使用P / Invoke来完成第一步:

  • AttachConsole(pid)附加到该控制台 - 如果您的进程已与控制台关联,则必须先使用FreeConsole,因为进程一次只能与一个控制台关联。
  • 现在您已经连接,使用CreateFile获取控制台输出句柄(“CONOUT $”,GENERIC_WRITE,FILE_SHARE_WRITE,...) - 可能能够在托管代码中执行此部分。
  • 现在您已经掌握了HANDLE,将其包装在托管代码中 - 这部分您已经知道了。

话虽如此,即使你能做到这一点,但这样做并不一定是个好主意。当你这样做时,没有什么可以阻止原始进程写入控制台,并且两者的输出混合起来,这取决于进程如何进行缓冲。如果你想做一些事情,比如告诉用户什么,不管哪个窗口是活动的,那么可能有更好的方法。

答案 2 :(得分:1)

如果打算写入父控制台(如果有),则可以使用带有ATTACH_PARENT_PROCESS参数的AttachConsole函数。 (见msdn attachconsole)

ATTACH_PARENT_PROCESS(DWORD)-1:使用当前进程父级的控制台

如果确实需要检查父进程,可以使用CreateToolhelp32Snapshot并通过PROCESSENTRY32结构的th32ParentProcessID成员获取父进程。

答案 3 :(得分:0)

系统进程由系统进程标识符唯一标识。与许多Windows资源一样,进程也由其句柄标识,该句柄在计算机上可能不是唯一的。句柄是资源标识符的通用术语。操作系统持久保存进程句柄,即使进程已退出,也可通过Process组件的Process.Handle属性进行访问。因此,您可以获取进程的管理信息,例如Process.ExitCode(通常为成功为零或非零错误代码)和Process.ExitTime。手柄是非常有价值的资源,因此泄漏的手柄比泄漏记忆更具毒性。

这不是你问题的确切答案,但它有助于你理解基本的事情。