如何获取控制台应用程序窗口的句柄?

55

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

7个回答

71

不确定是否有效,但你可以尝试一下:

IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;

3
对于任何想在2到22年后查询此信息的人;是的,这个方法有效(对我而言)。 - John Hoven
10
如果你的进程没有启动控制台,则似乎无法使用此功能。如果你在另一个控制台中运行它,你总会得到零。在其他情况下,看起来需要使用findwindowbycaption功能(参见http://support.microsoft.com/kb/124103)。 - John Gardner
1
这在我使用 Visual Studio 进行调试时有效,但是当我不进行调试运行时就无效了。在 bin/Release 中双击 myProgram.exe 是可行的,但 FindWindowByCaption 可能是更健壮的解决方案。 - Carl Walsh
1
之前我对这个窗口一直非常尊重,从来没有想过能够通过代码操纵它,但它只是一个简单的窗口,并没有与其他窗口有太大不同。上面那行 代码是有效的,你也可以通过 WinApi 枚举并找到它,然后对它进行各种疯狂的操作。只需小心,因为它有一个WndProc让它的调整大小有些不同。 - Bitterblue

32

以下是一种健壮的方法:

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(或多个这样的进程,每个会话一个,自Vista以来)管理,因此无法使用ReadProcessMemory等方式检索。所有csrss公开的只有CSRSS LPC API。完整API列表中只有一个相关函数SrvGetConsoleWindow。它不接受PID,但可以通过an alternative implementationwinsrv.dll中的函数反汇编确定调用方的PID。


5
这应该是被接受的答案。GetConsoleWindow 是正确的方法;这里的许多其他答案都很荒谬。 - James Johnston
2
这是我个人认为的正确答案,除非你想花时间弄清楚为什么在控制台运行应用程序与在IDE中运行时不起作用。谢谢Ivan。 - WiredEarp
1
请注意,官方的MS文档指出GetConsoleWindow()函数已经半废弃,虽然它仍然会得到支持,但不建议在新代码中使用。 - undefined
@PrincessRTFM 阅读了关于GetConsoleWindow的MSDN文章,实际上并不是他们所称的"弃用" -- 他们不会在未来的任何时候移除它。相反,他们建议使用终端控制序列 -- 这根本不需要获取控制台的窗口句柄。因此,这个事实与当前的问题并不相关。 - undefined

10

试试这个:

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

9
我刚刚为自己解决了这个问题(不幸的是在看到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)

因为控制台没有实现 IWin32Window,所以我不得不自己实现它,当然,只是为了在第一个参数中调用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的新方法中,而Main所做的只是创建一个新的Program并调用Start
private static void Main()
{
    new Program().Start();
}

private void Start()
{
    AppDomain.CurrentDomain.UnhandledException += CatchUnhandled;

    throw new Exception();
}

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

3
简而言之,答案是:[DllImport("kernel32.dll")] internal static extern IntPtr GetConsoleWindow(); (注:该语句为C#代码,用于获取控制台窗口的句柄) - Patrick from NDepend team

0

我认为这样的东西不存在。控制台窗口对应用程序不可访问。你可以尝试迭代进程列表,查找自己的进程名称。如果我没记错,Process类包含一个属性,用于获取程序的主窗口句柄,这个句柄可能是控制台应用程序的控制台窗口 - 我不确定。


"迭代进程列表查找自己的进程名称" => 不是非常高效的方法...你可以通过PID找到它,或者使用} ;) - Thomas Levesque
哎呀,Thomas Levesque的回答更加优雅。虽然也依赖于相同的属性,但他不需要迭代。我忘了你可以直接访问当前进程... - Thorsten Dittmar
@Thomas:抱歉,之前没有看到你的评论。当然,迭代要低效得多。我没记起来GetCurrentProcess()方法... - Thorsten Dittmar

0

在VB.Net中展示消息框置于控制台窗口之上的另一种版本

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

-1
在一个将诊断信息流式传输到控制台的控制台应用程序中,我想禁用鼠标输入,尝试使用GetConsoleWindow(),Process.GetCurrentProcess().MainWindowHandle和FindWindowByCaption(IntPtr.Zero, Console.Title)。每个都返回相同的非零句柄,但当我尝试在SetConsoleMode中使用该句柄时,它抛出“无效的句柄”异常。最后,我尝试了SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode | ENABLE_EXTENDED_FLAGS)),其中STD_INPUT_HANDLE被定义为-10,并且它起作用了。Microsoft的文档建议控制台句柄可能会重新分配,我对这个解决方案不太舒服或满意,但是到目前为止,这是我找到的唯一可以编程地禁用快速编辑模式的方法。GetStdHandle(STD_INPUT_HANDLE)返回'3',其他调用返回每次运行程序时都会变化的7位值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接