【问题标题】:How do I get the handle of a console application's window如何获取控制台应用程序窗口的句柄
【发布时间】:2010-11-19 15:19:39
【问题描述】:

谁能告诉我如何在 C# 中获取 Windows 控制台应用程序的句柄?在 Windows 窗体应用程序中,我通常会尝试 this.Handle

【问题讨论】:

    标签: c# console hwnd window-handles


    【解决方案1】:

    不确定它是否有效,但您可以尝试:

    IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;
    

    【讨论】:

    • 这对你有用吗,格兰特?只是好奇,因为我也可以在几个地方使用它:)
    • 对于 2-22 年后的其他人;是的,这确实有效(对我来说)。
    • 这似乎仅在您的进程启动控制台时才有效。如果你从另一个控制台运行它,你总是得到零。看起来您需要在其他情况下使用 findwindowbycaption(请参阅support.microsoft.com/kb/124103
    • 这在 Visual Studio 中调试时对我有用,但是当我在没有调试的情况下运行时它没有。在 bin/Release 中对 myProgram.exe 进行双倍计时确实有效,但 FindWindowByCaption 可能是更强大的解决方案。
    • 早些时候我一直非常尊重这个窗口,虽然我从来没有通过代码来操作它,但它是一个与其他窗口没有太大区别的简单窗口。上面的行有效,你也可以通过WinApi枚举和找到它,并对它做各种疯狂的事情。请注意,它有一个 WndProc,它的大小会有所不同。
    【解决方案2】:

    这是一种可靠的方法:

    来自Console Win32 API的相关函数有:

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AttachConsole(uint dwProcessId);
    [DllImport("kernel32.dll")]
    static extern IntPtr GetConsoleWindow();
    [DllImport("kernel32.dll", SetLastError=true, ExactSpelling=true)]
    static extern bool FreeConsole();
    
    • 对于当前进程所附加的控制台,GetConsoleWindow() 就足够了
    • 对于附加到另一个进程的控制台,使用AttachConsole 附加到它,调用GetConsoleWindow,它们立即使用FreeConsole 分离。

    为了格外小心,请在附加之前注册一个控制台事件处理程序(并在分离后取消注册),这样如果在您附加到控制台的微小时间范围内发生控制台事件,您就不会意外终止:

    [DllImport("kernel32.dll")]
    static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine,
       bool Add);
    delegate Boolean ConsoleCtrlDelegate(CtrlTypes CtrlType);
    enum CtrlTypes : uint {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,  
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }
    
    bool is_attached=false;    
    ConsoleCtrlDelegate ConsoleCtrlDelegateDetach = delegate(CtrlType) {
         if (is_attached = !FreeConsole())
             Trace.Error('FreeConsole on ' + CtrlType + ': ' + new Win32Exception());
         return true;
    };
    

    为了读取某些内容而对当前进程进行更改是相当难看的(当这是一个控制台进程时,这会变得非常难看,因为它需要一个辅助进程来避免终止当前的控制台)。然而,进一步的调查表明,除了注入csrss 进程或目标进程之外别无他法。

    控制台通信信息位于csrss.exe 并由csrss.exe 管理(或其中多个,每个会话一个,自Vista 以来),因此无法使用ReadProcessMemory 之类的方式检索它。 csrss 暴露的只是CSRSS LPC API。完整的 API 列表中只有一个相关函数,SrvGetConsoleWindow。它不接受 PID,但会确定调用方的 PID,如 an alternative implementationwinsrv.dll 中的函数反汇编。

    【讨论】:

    • 这应该是公认的答案。 GetConsoleWindow 是正确的方法;这里的许多其他答案都是愚蠢的。
    • 这是正确的答案恕我直言,除非您想花时间弄清楚为什么您的应用程序在从控制台而不是 IDE 运行时无法运行。谢谢伊万。
    【解决方案3】:

    试试这个:

    [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
    public static extern IntPtr FindWindowByCaption(IntPtr zeroOnly, string lpWindowName);
    …
    
    Console.Title = "Test";
    …
    
    IntPtr handle = FindWindowByCaption(IntPtr.Zero, Console.Title);
    

    【讨论】:

      【解决方案4】:

      我刚刚为自己解决了这个问题(不幸的是,在看到更快的Thomas's answer 之前)。好吧,对于那些对他的回答不满意的人来说,这是另一种方式。我写这个答案是因为如果您将控制台视为窗口,我想提供另一个答案 + 一种更好的方法来设计 Program 类。让我们从那个设计开始:

      我改变了Program 类的默认样式。实际上,我已经将它变成了一个包含程序的类,而不仅仅是一个表示它并使用其他类作为内容的方法。 (如果你不明白我的意思,那不重要)。

      我不得不这样做是因为我想编写以下事件处理程序:

      private void CatchUnhandled(Object sender, UnhandledExceptionEventArgs e)
      {
          var exception = e.ExceptionObject as Exception;
          MessageBox.Show(this, exception.Message, "Error"); // Check out 1st arg.
      }
      

      它重载了这个方法MessageBox.Show(IWin32Window, String, String)

      因为 Console 没有实现 IWin32Window,所以我必须自己实现它,当然,为了在 1st 参数中调用 this

      这是它的实现和其他所有内容:

      注意:此代码是从我的应用程序中复制粘贴的,您可以随意更改访问修饰符

      Program类声明:

      internal class Program : IWin32Window
      {
          ...
      }
      

      IWin32Window 实现:

      public IntPtr Handle
      {
          get { return NativeMethods.GetConsoleWindow(); }
      }
      

      它使用以下类:

      internal static class NativeMethods
      {
          [DllImport("kernel32.dll")]
          internal static extern IntPtr GetConsoleWindow();
      }
      

      现在,问题是您实际上不能在 Main 中调用 this,因为它是一个静态方法,所以无论在 Main 中是什么,我已经转移到一个名为 Start 的新方法和所有 @ 987654340@ 正在创建一个新的Program 并调用Start

      private static void Main()
      {
          new Program().Start();
      }
      
      private void Start()
      {
          AppDomain.CurrentDomain.UnhandledException += CatchUnhandled;
      
          throw new Exception();
      }
      

      当然,结果是一个消息框,我的控制台窗口作为所有者。
      将这种方法用于消息框,当然只是这种方法的一种应用。

      【讨论】:

      • 简而言之,答案是:[DllImport("kernel32.dll")] internal static extern IntPtr GetConsoleWindow();
      【解决方案5】:

      我不认为有这样的事情。应用程序无法访问控制台窗口。您可能会尝试迭代进程列表以查找您自己的进程名称。 Process 类 IIRC 包含程序主窗口句柄的属性,可能是控制台应用程序的控制台窗口 - 我不确定。

      【讨论】:

      • "迭代进程列表寻找你自己的进程名称" => 不是一个非常有效的方法...你可以通过 PID 找到它,或者使用 } ;)
      • 哎呀——Thomas Levesque 的回答更加优雅。在依赖相同的属性时,他不需要迭代。我忘了你可以直接访问当前进程...
      • @Thomas:抱歉,之前没有看到您的评论。当然,迭代的效率要低得多。我不记得 GetCurrentProcess() 方法...
      【解决方案6】:

      在将诊断流式传输到控制台的控制台应用程序中,我想禁用鼠标输入,我尝试了 GetConsoleWindow(), Process.GetCurrentProcess().MainWindowHandle, and FindWindowByCaption(IntPtr.Zero, Console.Title)。 它们中的每一个都返回相同的非零句柄,但是当我尝试在 SetConsoleMode 中使用该句柄时,它引发了“无效句柄”异常。最后,我尝试了SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode | ENABLE_EXTENDED_FLAGS)),将 STD_INPUT_HANDLE 定义为 -10,它成功了。 MS 的文档表明可以重新分配控制台的句柄,我对这个解决方案并不满意或不满意,但到目前为止,这是我发现的唯一允许我以编程方式禁用快速编辑模式的东西。 GetStdHandle(STD_INPUT_HANDLE) 返回 '3',其他调用返回一个 7 位数的值,每次运行程序都会变化。

      【讨论】:

        【解决方案7】:

        VB.Net 中关于如何在控制台窗口顶部显示 MessageBox 的另一个版本

        Imports System.Runtime.InteropServices
        Imports System.Windows.Forms
        
        Friend Module NativeMethods
            <DllImport("kernel32.dll")> Friend Function GetConsoleWindow() As IntPtr
            End Function
        End Module
        
        NotInheritable Class WndProxy
            Implements IWin32Window
            ReadOnly Property Handle As IntPtr Implements IWin32Window.Handle
            Sub New(_hwnd As IntPtr)
                Handle = _hwnd
            End Sub
        End Class
        
        Module Module1
        
            Sub Main()
                ' using MainWindowHandle
                Dim w = New WndProxy(Process.GetCurrentProcess().MainWindowHandle)
                MessageBox.Show(w, "Hi")
                ' using GetConsoleWindow api
                Dim s = New WndProxy(NativeMethods.GetConsoleWindow)
                MessageBox.Show(s, "Hi")
            End Sub
        
        End Module
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-10-04
          • 2018-07-23
          • 2019-11-19
          • 1970-01-01
          • 2023-03-26
          • 2022-10-12
          • 1970-01-01
          相关资源
          最近更新 更多