【问题标题】:Get the handle and write to the console that launched our process获取句柄并写入启动我们进程的控制台
【发布时间】:2011-04-19 03:27:34
【问题描述】:

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

    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 文件,因此这意味着有一种编程方式...

谢谢!

【问题讨论】:

    标签: c# c++ windows winapi


    【解决方案1】:

    我终于想出了如何在启动 windows 应用程序时透明地附加到控制台(如果它是前台窗口)。

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

    注意:控制台可以在向您显示应用程序消息时接受用户输入,但在 stderr 从您的应用程序输出时使用它会有点混乱。

    如果你从带有至少一个参数的控制台窗口启动你的应用程序,使用这个 sn-p 代码,它将附加 Console.Write 到它,如果你用参数 /debug 启动应用程序,那么它甚至会附加Debug.Write 到控制台。

    在退出应用程序之前调用 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();
            }    
        }        
    }
    

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

        如果您只想写入其他应用程序使用的控制台,那么您可以使用以下命令 - 您需要使用 P/Invoke 来执行第一步:

        • AttachConsole(pid) 附加到该控制台 - 如果您的进程已经与控制台相关联,您必须首先使用 FreeConsole,因为一个进程一次只能与一个控制台相关联。
        • 现在您已连接,使用 CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, ... ) 获取控制台输出句柄 - 或许可以在托管代码中完成此部分。
        • 现在您已经获得了 HANDLE,将它封装在托管代码中 - 这部分您已经知道了。

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

        【讨论】:

          【解决方案4】:

          系统进程通过其进程标识符在系统上唯一标识。与许多 Windows 资源一样,进程也由其句柄标识,该句柄在计算机上可能不是唯一的。句柄是资源标识符的通用术语。操作系统会保留进程句柄,该句柄可通过 Process 组件的 Process.Handle 属性访问,即使进程已退出也是如此。因此,您可以获得进程的管理信息,例如 Process.ExitCode(通常为零表示成功或非零错误代码)和 Process.ExitTime。句柄是一种极其宝贵的资源,因此泄漏句柄比泄漏内存更致命。

          这不是你问题的确切答案,但它可以帮助你真正理解基本的东西。

          【讨论】:

          • 我知道句柄是微妙的,但我不会增加对句柄做坏事的引用计数。当所有者进程释放句柄时,我的代码将简单地失败......这是我所期望的。没有办法写入进程的stdout(在windows上就是因为在linux上可以)?
          • 是的,我知道在 linux 上我们可以,但我认为现在需要进行一些 COM 研究,因为我确信有办法通过 COM 来做到这一点。如果我发现相关的东西会通知你。同时,问题和主题对我来说都很有趣。
          猜你喜欢
          • 1970-01-01
          • 2018-07-23
          • 2010-11-28
          • 1970-01-01
          • 2010-11-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-23
          相关资源
          最近更新 更多