Может ли кто-нибудь сказать мне, как получить дескриптор консольного приложения Windows на С#? В приложении Windows Forms я обычно пробовал this.Handle
.
Как получить дескриптор окна консольного приложения
Ответы (7)
Не уверен, что это работает, но вы можете попробовать это:
IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;
FindWindowByCaption
может быть более надежным решением.
- person Carl Walsh; 20.07.2013
WndProc
, который немного меняет размер.
- person Bitterblue; 19.08.2013
- Вышеупомянутый метод
Process.MainWindowHandle
работает только для процесса, запустившего консоль - Метод
FindWindowByCaption
по своей сути ненадежен.
Вот надежный способ сделать это:
Связанные функции из Консольный 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, но определяет идентификатор вызывающей стороны, как показано в альтернативная реализация или дизассемблирование функции в winsrv.dll
.
Попробуй это:
[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);
Я только что решил эту проблему для себя (к сожалению, до того, как увидел ответ Томаса, который намного быстрее). Ну вот еще способ для тех, кого его ответ не устраивает. Я пишу этот ответ, потому что хочу дать другой ответ + лучший способ разработки класса 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
в 1st аргументе.
Вот реализация этого и всего остального:
Примечание: этот код скопирован из моего приложения, вы можете свободно изменять модификаторы доступа
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();
}
Теперь проблема в том, что вы не можете на самом деле вызвать this
в Main
, поскольку это статический метод, поэтому то, что было в Main
, я перешел к новому методу с именем Start
, и все, что делает Main
, это создает новый Program
и вызывает Start
.
private static void Main()
{
new Program().Start();
}
private void Start()
{
AppDomain.CurrentDomain.UnhandledException += CatchUnhandled;
throw new Exception();
}
Результатом было, конечно, окно сообщений с окном моей консоли в качестве владельца.
Использование этого метода для окна сообщения, конечно, только одно применение этого метода.
Я не думаю, что есть такая вещь. Окно консоли недоступно для приложения. Вы МОЖЕТЕ попытаться просмотреть список процессов в поисках собственного имени процесса. Класс Process
IIRC содержит свойство для дескриптора главного окна программы, которое может быть окном консоли для консольных приложений, в чем я не уверен.
В консольном приложении, которое транслировало диагностику на консоль и для которого я хотел отключить ввод с помощью мыши, я попробовал 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-значное значение, которое изменяется при каждом запуске программы.
Еще одна версия в 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