[英]How do I get the handle of a console application's window
Can someone tell me how to get the handle of a Windows console application in C#?有人能告诉我如何在 C# 中获取 Windows 控制台应用程序的句柄吗? In a Windows Forms application, I would normally try
this.Handle
.在 Windows 窗体应用程序中,我通常会尝试
this.Handle
。
不确定它是否有效,但您可以尝试:
IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;
Process.MainWindowHandle
method only works for the process that started the consoleProcess.MainWindowHandle
方法只适用于启动控制台的进程FindWindowByCaption
method is inherently unreliable FindWindowByCaption
方法本质上是不可靠的Here's a robust way to do this:这是一个强大的方法来做到这一点:
The related functions from the Console Win32 API are: 控制台 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()
is enoughGetConsoleWindow()
就足够了AttachConsole
, call GetConsoleWindow
, them immediately detach with FreeConsole
.AttachConsole
附加到它,调用GetConsoleWindow
,它们立即与FreeConsole
分离。 For the extra cautious, register a console event handler before attaching (and unregister it after detaching) as well so you don't accidentally get terminated if a console event happens in the tiny time frame you're attached to the console:为了更加谨慎,在附加之前注册控制台事件处理程序(并在分离后取消注册),这样如果控制台事件发生在您附加到控制台的微小时间范围内,您就不会意外终止:
[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;
};
Making changes to the current process just to read something is rather ugly (when this is a console process, this gets really ugly since it requires a helper process to avoid terminating the current console).改变当前进程只是为了读取一些东西是相当丑陋的(当这是一个控制台进程时,这变得非常丑陋,因为它需要一个辅助进程来避免终止当前控制台)。 Nevertheless, further investigation shows that there's no other way short of injecting into the
csrss
process or the target process.尽管如此,进一步的调查表明,除了注入
csrss
进程或目标进程之外, csrss
法。
Console correspondence information is located in and managed by csrss.exe
(or a multitude of those, one for each session, since Vista), so it can't be retrieved with the likes of ReadProcessMemory
.控制台通信信息位于并由
csrss.exe
管理(或多个,每个会话一个,自 Vista 以来),因此无法使用ReadProcessMemory
方法检索它。 All that csrss
exposes is the CSRSS LPC API . csrss
公开的所有csrss
都是CSRSS LPC API 。 There's only one relevant function in the full API list, SrvGetConsoleWindow
.在完整的 API 列表中只有一个相关函数
SrvGetConsoleWindow
。 And it doesn't accept a PID but determines that of the calling party as seen in an alternative implementation or the function's disassembly in winsrv.dll
.并且它不接受 PID,而是确定调用方的 PID,如替代实现或
winsrv.dll
的函数反汇编winsrv.dll
。
Try this:试试这个:
[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);
I've just solved this problem for myself (unfortunately before seeing Thomas's answer which is much quicker).我刚刚为自己解决了这个问题(不幸的是,在看到托马斯更快的答案之前)。 Well, here's another way for those who are not satisfied with his answer.
好吧,对于那些对他的回答不满意的人,这是另一种方式。 I'm writing this answer because I want to provide another answer + a better way to design the
Program
class if you're treating your console as a Window.我写这个答案是因为如果您将控制台视为窗口,我想提供另一个答案 + 设计
Program
类的更好方法。 Let's begin with that design:让我们从这个设计开始:
I've kind of changed the default style of the Program
class.我已经改变了
Program
类的默认样式。 I've actually made it into a class that has a program in it, and not just one method which represent it and uses other classes for content.我实际上已经将它变成了一个包含程序的类,而不仅仅是一种表示它并使用其他类作为内容的方法。 (If you don't know what I mean, not important).
(如果你不明白我的意思,不重要)。
The reason I had to do it is because I wanted to write the following event handler:我必须这样做的原因是因为我想编写以下事件处理程序:
private void CatchUnhandled(Object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
MessageBox.Show(this, exception.Message, "Error"); // Check out 1st arg.
}
It overloads this method MessageBox.Show(IWin32Window, String, String)
.它重载了这个方法
MessageBox.Show(IWin32Window, String, String)
。
Because Console doesn't implement IWin32Window
, I had to implement it myself, of course, in order to just call this
in the 1 st argument.由于控制台没有实现
IWin32Window
,我必须实现它自己,当然,为了只是调用this
在1个变量。
Here is the implementation of it and everything else:这是它的实现以及其他所有内容:
Note: this code is copy-pasted from my application, you can feel free to change the access modifiers注意:此代码是从我的应用程序复制粘贴的,您可以随意更改访问修饰符
Program
Class Declaration: Program
类声明:
internal class Program : IWin32Window
{
...
}
IWin32Window
Implementation: IWin32Window
实现:
public IntPtr Handle
{
get { return NativeMethods.GetConsoleWindow(); }
}
It uses the following class:它使用以下类:
internal static class NativeMethods
{
[DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow();
}
Now, the problem is that you can't actually call this
in Main
, being a static method, so whatever was in Main
I've moved to a new method named Start
and all Main
is doing is creating a new Program
and calling Start
.现在,问题是你实际上不能在
Main
调用this
,因为它是一个静态方法,所以无论Main
是什么,我都移到了一个名为Start
的新方法, Main
所做的就是创建一个新的Program
并调用Start
。
private static void Main()
{
new Program().Start();
}
private void Start()
{
AppDomain.CurrentDomain.UnhandledException += CatchUnhandled;
throw new Exception();
}
The result was, of course, a message-box with my console's window as an owner.结果当然是一个消息框,我的控制台窗口作为所有者。
Using this method for a message-box, is of course only one application of this method.将这种方法用于消息框,当然只是这种方法的一种应用。
I don't think there is such a thing.我不认为有这样的事情。 The console window is not accessible to the application.
应用程序无法访问控制台窗口。 You MIGHT try to iterate the process list looking for your own process name.
您可能会尝试迭代进程列表以查找您自己的进程名称。 The
Process
class IIRC contains a property for the program's main window handle, which might be the console window for console applications - which I'm not sure of. Process
类 IIRC 包含程序主窗口句柄的属性,它可能是控制台应用程序的控制台窗口 - 我不确定。
In a console application which streamed diagostics to the console, and for which I wanted to disable mouse input, I tried GetConsoleWindow(), Process.GetCurrentProcess().MainWindowHandle, and FindWindowByCaption(IntPtr.Zero, Console.Title)
.在将诊断流式传输到控制台并且我想禁用鼠标输入的控制台应用程序中,我尝试了
GetConsoleWindow(), Process.GetCurrentProcess().MainWindowHandle, and FindWindowByCaption(IntPtr.Zero, Console.Title)
。 Each of these returned the same non-zero handle, but when I tried to use that handle in SetConsoleMode it threw a "Invalid Handle" exception.每个都返回相同的非零句柄,但是当我尝试在 SetConsoleMode 中使用该句柄时,它抛出了“无效句柄”异常。 Finally I tried
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode | ENABLE_EXTENDED_FLAGS))
with STD_INPUT_HANDLE defined as -10, and it worked.最后,我尝试了
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode | ENABLE_EXTENDED_FLAGS))
并将 STD_INPUT_HANDLE 定义为 -10,并且它起作用了。 MS's documentation suggests that handles to consoles may be reassigned, and I am not comfortable or happy with this solution, but so far it is the only thing I've found that allows me to disable quick edit mode programmatically. MS 的文档表明可能会重新分配控制台的句柄,我对这个解决方案不满意或不满意,但到目前为止,这是我发现的唯一允许我以编程方式禁用快速编辑模式的方法。
GetStdHandle(STD_INPUT_HANDLE)
returns '3', the other calls return a 7 digit value that varies each time the program is run. GetStdHandle(STD_INPUT_HANDLE)
返回“3”,其他调用返回一个 7 位数的值,该值在每次程序运行时都会发生变化。
One more version in VB.Net on how to show MessageBox on top of console window 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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.