WM_CLOSE message does not seem to close chrome - c#

I am trying to close chrome from my application.
I am using the following method:
public class CloseChrome
{
static int WM_CLOSE = 0x0010;
static int WM_QUIT = 0x0012;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
static public void closeCrome()
{
var process = Process.GetProcesses();
foreach (var item in process)
{
if (item.ProcessName.Contains("chrome"))
{
//item.Kill();
int ret = SendMessage(item.Handle, WM_CLOSE, 0, 0);
Console.WriteLine("Chrome Reply: " + a);
}
}
}
}
Using the kill method kills the chrome process, but when I send the WM_CLOSE message nothing happens even though the return value is 0 from sendMessage. Am I doing something wrong is chrome just ignoring my request?

Process.Handle will be the HANDLE of the process, not the HWND of the main window. In fact a process can own many windows, so the concept of a main window doesn't really apply.
What you need to do is call the (csharp equivalent of) EnumWindows and then call GetWindowProcessThreadId to test each HWND to see if it belongs to your target process.
The more usual alternative is to examine a target window with Spy++ to see if the class name is pretty unique, and if it is, you can use FindWindow.

Related

Catch new process event before the main thread starts to run

I am developing an application to monitor new running processes before the main thread starts running. The problem is that the code below is executed when all threads in the process are loaded and the process is doing its job. I want to do something like the "sCreateProcessNotifyRoutineEx" function from C++ in C#. Please help with this
What I have tried:
[DllImport("kernel32")]
public extern static int OpenProcess(int access, bool inherit, int pid);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, IntPtr lpNumberOfBytesRead);
ManagementEventWatcher processStartEvent = new ManagementEventWatcher("SELECT * FROM Win32_ProcessStartTrace");
public Service1()
{
InitializeComponent();
processStartEvent.EventArrived += new EventArrivedEventHandler(processStartEvent_EventArrived);
processStartEvent.Start();
}
public async void processStartEvent_EventArrived(object sender, EventArrivedEventArgs e)
{
try
{
string processName = e.NewEvent.Properties["ProcessName"].Value.ToString();
string processID = Convert.ToInt32(e.NewEvent.Properties["ProcessID"].Value).ToString();
int pid = Convert.ToInt32(e.NewEvent.Properties["ProcessID"].Value);
Process p = Process.GetProcessById(pid);
string wholeFileName = p.MainModule.FileName;
await AnalyzeStartup(wholeFileName, p);
}
catch { }
}
If your code started the process, you could start it suspended (or more technically correct, with a suspended main thread).
In this case, since your code is not starting the process, the only reliable way to do this is with system-wide DLL injection. Detours is by far the best library to use for this. To use it from C#, you'll probably need to write a non-trivial p/Invoke layer. You could try the unofficial wrapper, but this is an extremely complex problem to solve; YMMV.

emulate CTRL+C in process C# .NET [duplicate]

I'm writing a wrapper class for a command line executable. This exe accepts input from stdin until I hit Ctrl+C in the command prompt shell, in which case it prints output to stdout based on the input. I want to simulate that Ctrl+C press in C# code, sending the kill command to a .NET Process object. I've tried calling Process.Kill(), but that doesn't seem to give me anything in the process's StandardOutput StreamReader. Might there be anything I'm not doing right? Here's the code I'm trying to use:
ProcessStartInfo info = new ProcessStartInfo(exe, args);
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
p.StandardInput.AutoFlush = true;
p.StandardInput.WriteLine(scriptcode);
p.Kill();
string error = p.StandardError.ReadToEnd();
if (!String.IsNullOrEmpty(error))
{
throw new Exception(error);
}
string output = p.StandardOutput.ReadToEnd();
The output is always empty, even though I get data back from stdout when I run the exe manually.
Edit: This is C# 2.0 by the way.
Despite the fact that using GenerateConsoleCtrlEvent() for sending Ctrl+C signal is the right answer, it needs significant clarification to get it to work in different .NET application types.
If your .NET application doesn't use its own console (Windows Forms/WPF/Windows Service/ASP.NET), the basic flow is:
Attach the main .NET process to the console of the process that you want to signal with Ctrl+C.
Prevent the main .NET process from stopping because of Ctrl+C event by disabling handling of the signal with SetConsoleCtrlHandler().
Generate the console event for the current console with GenerateConsoleCtrlEvent() (processGroupId should be zero! The answer with code that sends p.SessionId will not work and is incorrect).
Wait for the signaled process to respond (e.g. by waiting for it to exit)
Restore Ctrl+C handling by main process and disconnect from console.
The following code snippet illustrates how to do that:
Process p;
if (AttachConsole((uint)p.Id)) {
SetConsoleCtrlHandler(null, true);
try {
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0))
return false;
p.WaitForExit();
} finally {
SetConsoleCtrlHandler(null, false);
FreeConsole();
}
return true;
}
where SetConsoleCtrlHandler(), FreeConsole(), AttachConsole() and GenerateConsoleCtrlEvent() are native WinAPI methods:
internal const int CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(uint CtrlType);
Note that waiting for the targeted process to respond, typically by waiting for the process to exit, is critical. Otherwise, the Ctrl+C signal will remain in the current process's input queue and when handling is restored by the second call to SetConsoleCtrlHandler(), that signal will terminate the current process, rather than the targeted one.
Things become more complex if you need to send Ctrl+C from .NET console application. The above approach will not work because AttachConsole() returns false in this case (the main console app already has a console). It is possible to call FreeConsole() before AttachConsole() call, but doing so will result in the original .NET app console being lost, which is not acceptable in most cases.
Here is my solution for this case; it works and has no side effects for the .NET main process console:
Create small supporting .NET console program that accepts process ID from command line arguments, loses its own console with FreeConsole() before the AttachConsole() call and sends Ctrl+C to the target process with code mentioned above.
The main .NET console process just invokes this utility in a new process when it needs to send Ctrl+C to another console process.
I've actually just figured out the answer. Thank you both for your answers, but it turns out that all i had to do was this:
p.StandardInput.Close()
which causes the program I've spawned to finish reading from stdin and output what i need.
#alonl: The user is attempting to wrap a command-line program. Command-line programs don't have message pumps unless they are specifically created, and even if that was the case, Ctrl+C doesn't have the same semantics in a Windows-environment application (copy, by default) as it does in a command-line environment (Break).
I threw this together. CtrlCClient.exe simply calls Console.ReadLine() and waits:
static void Main(string[] args)
{
ProcessStartInfo psi = new ProcessStartInfo("CtrlCClient.exe");
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
proc.StandardInput.WriteLine("\x3");
Console.WriteLine(proc.StandardOutput.ReadToEnd());
Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
Console.ReadLine();
}
My output seems to do what you want:
4080 is active: True
4080 is active: False
Hope that helps!
(To clarify: \x3 is the hex escape sequence for the hex character 3, which is Ctrl+C. It's not just a magic number. ;) )
Ok, here is a solution.
The way to send the Ctrl-C signal is with GenerateConsoleCtrlEvent. HOWEVER, this call takes a processGroupdID parameter, and sends the Ctrl-C signal to all processes in the group. This would be fine if it weren't for the fact that there is no way spawn child process in .net that is in a different process group than you (the parent) are in. So, when you send the GenerateConsoleCtrlEvent, both the child AND YOU (THE PARENT) GET IT. So, you need to capture the Ctrl-C event in the parent too, and then determine if you ned to ignore it not.
In my case, I want the parent to be able to handle Ctrl-C events also, so I need to distnguish between Ctrl-C events sent by the user on the console, and those sent by the parent process to the child. I do this by just hackishly setting/unsetting a boolean flag while send the Ctrl-C to the child, and then checking for this flag in the parent's Ctrl-C event handler (ie. if send Ctrl-C to child, then ignore.)
So, the code would look something like this:
//import in the declaration for GenerateConsoleCtrlEvent
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);
public enum ConsoleCtrlEvent
{
CTRL_C = 0,
CTRL_BREAK = 1,
CTRL_CLOSE = 2,
CTRL_LOGOFF = 5,
CTRL_SHUTDOWN = 6
}
//set up the parents CtrlC event handler, so we can ignore the event while sending to the child
public static volatile bool SENDING_CTRL_C_TO_CHILD = false;
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
e.Cancel = SENDING_CTRL_C_TO_CHILD;
}
//the main method..
static int Main(string[] args)
{
//hook up the event handler in the parent
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
//spawn some child process
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.Arguments = "childProcess.exe";
Process p = new Process();
p.StartInfo = psi;
p.Start();
//sned the ctrl-c to the process group (the parent will get it too!)
SENDING_CTRL_C_TO_CHILD = true;
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, p.SessionId);
p.WaitForExit();
SENDING_CTRL_C_TO_CHILD = false;
//note that the ctrl-c event will get called on the parent on background thread
//so you need to be sure the parent has handled and checked SENDING_CTRL_C_TO_CHILD
already before setting it to false. 1000 ways to do this, obviously.
//get out....
return 0;
}
FWIW, in my case, what I wanted is, from a console process, create a child console process (ffmpeg.exe for that matter) and support clean CTRL-C handling in my process and in the child process (ffmpreg exits normally when CTRL-C is pressed which is a nice feature I wanted to keep working)
None of the solution I found here were working, so I just interop'd Windows' CreateProcess function and it just works w/o any effort, CTRL-C is automatically received by the child app and by the parent app, input and output streams are shared, etc. I was not able to reproduce that kind of code using the standard .NET Process class:
static void RunFFMpeg(string arguments)
{
var startup = new STARTUPINFO();
startup.cb = Marshal.SizeOf<STARTUPINFO>();
if (!CreateProcess(null, "ffmpeg.exe " + arguments, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref startup, out var info))
throw new Win32Exception(Marshal.GetLastWin32Error());
CloseHandle(info.hProcess);
CloseHandle(info.hThread);
var process = Process.GetProcessById(info.dwProcessId);
Console.CancelKeyPress += (s, e) =>
{
process.WaitForExit();
Console.WriteLine("Abort.");
// end of program is here
};
process.WaitForExit();
Console.WriteLine("Exit.");
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[DllImport("kernel32")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
int dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
Try actually sending the Key Combination Ctrl+C, instead of directly terminating the process:
[DllImport("user32.dll")]
public static extern int SendMessage(
int hWnd, // handle to destination window
uint Msg, // message
long wParam, // first message parameter
long lParam // second message parameter
);
Look it up on the MSDN, you should find what you need there in order to send the Ctrl+Key combination...
I know that the message you need for sending Alt+Key is WM_SYSTEMKEYDOWN and WM_SYSTEMKEYUP, can't tell you about Ctrl...

PostMessage to Current Active Process doesn't work for all process?

I have this code:
const UInt32 WM_KEYDOWN = 0x0100;
const int VK_DOWN = 0x28;
const int VK_UP = 0x26;
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern Int32 GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
private static Process GetProcessByHandle(IntPtr hwnd)
{
try
{
uint processID;
GetWindowThreadProcessId(hwnd, out processID);
return Process.GetProcessById((int)processID);
}
catch { return null; }
}
public static Process GetActiveProcess()
{
IntPtr hwnd = GetForegroundWindow();
return hwnd != null ? GetProcessByHandle(hwnd) : null;
}
public static void KeyUp()
{
PostMessage(GetActiveProcess().MainWindowHandle, WM_KEYDOWN, VK_UP, 0);
}
public static void KeyDown()
{
PostMessage(GetActiveProcess().MainWindowHandle, WM_KEYDOWN, VK_DOWN, 0);
}
When i call the KeyUp/KeyDown it works only on some process, proccess like text editos, visual studio.
But doesn't work on Explorer.exe, iTunes.exe, Chrome.exe, Outlook.exe, FileZilla.exe and more..
I ran debug on console:
while (true)
{
Process currentProcess = GetActiveProcess();
if (currentProcess != null)
Console.WriteLine(currentProcess.MainWindowTitle);
Thread.Sleep(1000);
}
Here what I get from some applications:
ProjName - Microsoft Visual Studio (Administrator)
?PostMessage to Current Active Proccess doesn't work for all proccess ? - StackOverFlow - Google Chrome?
FileZilla
Inbox - Outlook Data File - Microsoft Outlook
And on Explorer.exe i get empty line..
There are multiple ways to read keyboard state in a Windows application, and responding to WM_KEYDOWN and WM_KEYUP is just one of them. Applications can also use GetKeyState or GetAsyncKeyState. Applications can also respond to the WM_CHAR, WM_SYSKEYDOWN and WM_SYSKEYUP messages. Different applications will use different methods and if you are not simulating the one the application is looking for, it won't respond.
The problem you are having is slightly different than the one posed in the question that Raymond linked to, but the solution may be the same. Namely using SendInput instead of PostMessage

Broadcast Windows HWND_BROADCAST message

I am working in an application in this application I shall wait for some event in application 1 and when this event happen I shall sendmessage to application 2 which will perform something.
First API declaration
private const int HWND_BROADCAST = 0xffff;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int RegisterWindowMessage(string lpString);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SendNotifyMessage(int hWnd, int Msg, int wParam, int lParam);
Application 1 Code
private string msgstr = "MYMESSAGE";
public int msg = RegisterWindowMessage(msgstr);
if (msg == 0)
{
MessageBox.Show(Marshal.GetLastWin32Error().ToString());
}
//SendNotifyMessage(HWND_BROADCAST, msg, 4848484, 8484865);
SendNotifyMessage(HWND_BROADCAST, msg, 0, 0);
MessageBox.Show(Marshal.GetLastWin32Error().ToString());
Application 2 Code
static readonly int msg = RegisterWindowMessage("MYMESSAGE");
protected override void WndProc(ref Message m)
{
if (m.Msg == msg)
{
MessageBox.Show(m.Msg.ToString() + " = from wndproc");
}
base.WndProc(ref m);
}
Will somebody point out what is problem with this code.
I suspect there is problem in SendNotifyMessage
lparam and wparam
parameters
Will somebody suggest me any other alternative to achieve this behaviour!
There exists no such thing as code with a problem. There only exists code that does not do what you expected it to do. If you want us to tell you what the problem is with the above code, you should tell us what you expected it to do, (okay, we can guess that,) but most importantly, what it did instead. Did you get an error? Did it just silently fail to work? That's an important part of a question, you know!
You are making use of methods like RegisterWindowMessage() and SendNotifyMessage() the definitions of which you are not showing to us. How are we supposed to tell whether the problem is with SendNotifyMessage() when we do not know how SendNotifyMessage() has been declared?
Your Application 2 has a WndProc in which you expect to receive messages for a window. Has it been properly registered? Are you sure that it works? Does it receive other window messages? Does it receive "MYMESSAGE" if you send it from within Application 2?
HWND_BROADCAST only sends messages to top-level windows. Are you sure your window is a top-level window?
You are not checking whether the call to RegisterWindowMessage() in Application 2 was successful or not. How about checking that first of all?
"MYMESSAGE" is not a very good name for a message. How about using something more unique, like your first name plus last name, or creating a guid and using its string representation as a name for your message?
HWND_BROADCAST is pretty dangerous.. I know it's highly unlikely but what if another application also handled your message??
Anyway, that aside, have you taken a read of http://msdn.microsoft.com/en-us/library/ms644953.aspx
The most basic way to debug an issue with your code (as it's WINAPI based) would be to use GetLastError. You should always be checking the return value of methods to see if they succeed, so ensure it's returning zero (which means it worked). If it's not, and you get an error such as access denied, try running with either UAC Disabled or as an Administrator (Vista+).
When a message is blocked by UIPI the
last error, retrieved with
GetLastError, is set to 5 (access
denied).
The following code works very well,
Server side.
public partial class Server : Form
{
private UInt32 msg;
public Server()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
msg = RegisterWindowMessage("THIS_IS_MY_UNIQUE_MESSAGE");
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SendNotifyMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private void SendMessage(object sender, EventArgs e)
{
var retval = SendNotifyMessage(new IntPtr(-1), msg, 0, 0);
}
}
Client Side
public partial class Client : Form
{
public Client()
{
InitializeComponent();
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint RegisterWindowMessage(string lpString);
private static UInt32 GetMessage()
{
return RegisterWindowMessage("THIS_IS_MY_UNIQUE_MESSAGE");
}
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)GetMessage())
{
MessageBox.Show(#"Hello, from server");
}
base.WndProc(ref m);
}
}

C#: send ctrl+c to console program started with Process.Start()? [duplicate]

I'm writing a wrapper class for a command line executable. This exe accepts input from stdin until I hit Ctrl+C in the command prompt shell, in which case it prints output to stdout based on the input. I want to simulate that Ctrl+C press in C# code, sending the kill command to a .NET Process object. I've tried calling Process.Kill(), but that doesn't seem to give me anything in the process's StandardOutput StreamReader. Might there be anything I'm not doing right? Here's the code I'm trying to use:
ProcessStartInfo info = new ProcessStartInfo(exe, args);
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
p.StandardInput.AutoFlush = true;
p.StandardInput.WriteLine(scriptcode);
p.Kill();
string error = p.StandardError.ReadToEnd();
if (!String.IsNullOrEmpty(error))
{
throw new Exception(error);
}
string output = p.StandardOutput.ReadToEnd();
The output is always empty, even though I get data back from stdout when I run the exe manually.
Edit: This is C# 2.0 by the way.
Despite the fact that using GenerateConsoleCtrlEvent() for sending Ctrl+C signal is the right answer, it needs significant clarification to get it to work in different .NET application types.
If your .NET application doesn't use its own console (Windows Forms/WPF/Windows Service/ASP.NET), the basic flow is:
Attach the main .NET process to the console of the process that you want to signal with Ctrl+C.
Prevent the main .NET process from stopping because of Ctrl+C event by disabling handling of the signal with SetConsoleCtrlHandler().
Generate the console event for the current console with GenerateConsoleCtrlEvent() (processGroupId should be zero! The answer with code that sends p.SessionId will not work and is incorrect).
Wait for the signaled process to respond (e.g. by waiting for it to exit)
Restore Ctrl+C handling by main process and disconnect from console.
The following code snippet illustrates how to do that:
Process p;
if (AttachConsole((uint)p.Id)) {
SetConsoleCtrlHandler(null, true);
try {
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0))
return false;
p.WaitForExit();
} finally {
SetConsoleCtrlHandler(null, false);
FreeConsole();
}
return true;
}
where SetConsoleCtrlHandler(), FreeConsole(), AttachConsole() and GenerateConsoleCtrlEvent() are native WinAPI methods:
internal const int CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(uint CtrlType);
Note that waiting for the targeted process to respond, typically by waiting for the process to exit, is critical. Otherwise, the Ctrl+C signal will remain in the current process's input queue and when handling is restored by the second call to SetConsoleCtrlHandler(), that signal will terminate the current process, rather than the targeted one.
Things become more complex if you need to send Ctrl+C from .NET console application. The above approach will not work because AttachConsole() returns false in this case (the main console app already has a console). It is possible to call FreeConsole() before AttachConsole() call, but doing so will result in the original .NET app console being lost, which is not acceptable in most cases.
Here is my solution for this case; it works and has no side effects for the .NET main process console:
Create small supporting .NET console program that accepts process ID from command line arguments, loses its own console with FreeConsole() before the AttachConsole() call and sends Ctrl+C to the target process with code mentioned above.
The main .NET console process just invokes this utility in a new process when it needs to send Ctrl+C to another console process.
I've actually just figured out the answer. Thank you both for your answers, but it turns out that all i had to do was this:
p.StandardInput.Close()
which causes the program I've spawned to finish reading from stdin and output what i need.
#alonl: The user is attempting to wrap a command-line program. Command-line programs don't have message pumps unless they are specifically created, and even if that was the case, Ctrl+C doesn't have the same semantics in a Windows-environment application (copy, by default) as it does in a command-line environment (Break).
I threw this together. CtrlCClient.exe simply calls Console.ReadLine() and waits:
static void Main(string[] args)
{
ProcessStartInfo psi = new ProcessStartInfo("CtrlCClient.exe");
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
proc.StandardInput.WriteLine("\x3");
Console.WriteLine(proc.StandardOutput.ReadToEnd());
Console.WriteLine("{0} is active: {1}", proc.Id, !proc.HasExited);
Console.ReadLine();
}
My output seems to do what you want:
4080 is active: True
4080 is active: False
Hope that helps!
(To clarify: \x3 is the hex escape sequence for the hex character 3, which is Ctrl+C. It's not just a magic number. ;) )
Ok, here is a solution.
The way to send the Ctrl-C signal is with GenerateConsoleCtrlEvent. HOWEVER, this call takes a processGroupdID parameter, and sends the Ctrl-C signal to all processes in the group. This would be fine if it weren't for the fact that there is no way spawn child process in .net that is in a different process group than you (the parent) are in. So, when you send the GenerateConsoleCtrlEvent, both the child AND YOU (THE PARENT) GET IT. So, you need to capture the Ctrl-C event in the parent too, and then determine if you ned to ignore it not.
In my case, I want the parent to be able to handle Ctrl-C events also, so I need to distnguish between Ctrl-C events sent by the user on the console, and those sent by the parent process to the child. I do this by just hackishly setting/unsetting a boolean flag while send the Ctrl-C to the child, and then checking for this flag in the parent's Ctrl-C event handler (ie. if send Ctrl-C to child, then ignore.)
So, the code would look something like this:
//import in the declaration for GenerateConsoleCtrlEvent
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);
public enum ConsoleCtrlEvent
{
CTRL_C = 0,
CTRL_BREAK = 1,
CTRL_CLOSE = 2,
CTRL_LOGOFF = 5,
CTRL_SHUTDOWN = 6
}
//set up the parents CtrlC event handler, so we can ignore the event while sending to the child
public static volatile bool SENDING_CTRL_C_TO_CHILD = false;
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
e.Cancel = SENDING_CTRL_C_TO_CHILD;
}
//the main method..
static int Main(string[] args)
{
//hook up the event handler in the parent
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
//spawn some child process
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.Arguments = "childProcess.exe";
Process p = new Process();
p.StartInfo = psi;
p.Start();
//sned the ctrl-c to the process group (the parent will get it too!)
SENDING_CTRL_C_TO_CHILD = true;
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, p.SessionId);
p.WaitForExit();
SENDING_CTRL_C_TO_CHILD = false;
//note that the ctrl-c event will get called on the parent on background thread
//so you need to be sure the parent has handled and checked SENDING_CTRL_C_TO_CHILD
already before setting it to false. 1000 ways to do this, obviously.
//get out....
return 0;
}
FWIW, in my case, what I wanted is, from a console process, create a child console process (ffmpeg.exe for that matter) and support clean CTRL-C handling in my process and in the child process (ffmpreg exits normally when CTRL-C is pressed which is a nice feature I wanted to keep working)
None of the solution I found here were working, so I just interop'd Windows' CreateProcess function and it just works w/o any effort, CTRL-C is automatically received by the child app and by the parent app, input and output streams are shared, etc. I was not able to reproduce that kind of code using the standard .NET Process class:
static void RunFFMpeg(string arguments)
{
var startup = new STARTUPINFO();
startup.cb = Marshal.SizeOf<STARTUPINFO>();
if (!CreateProcess(null, "ffmpeg.exe " + arguments, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref startup, out var info))
throw new Win32Exception(Marshal.GetLastWin32Error());
CloseHandle(info.hProcess);
CloseHandle(info.hThread);
var process = Process.GetProcessById(info.dwProcessId);
Console.CancelKeyPress += (s, e) =>
{
process.WaitForExit();
Console.WriteLine("Abort.");
// end of program is here
};
process.WaitForExit();
Console.WriteLine("Exit.");
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[DllImport("kernel32")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
int dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
Try actually sending the Key Combination Ctrl+C, instead of directly terminating the process:
[DllImport("user32.dll")]
public static extern int SendMessage(
int hWnd, // handle to destination window
uint Msg, // message
long wParam, // first message parameter
long lParam // second message parameter
);
Look it up on the MSDN, you should find what you need there in order to send the Ctrl+Key combination...
I know that the message you need for sending Alt+Key is WM_SYSTEMKEYDOWN and WM_SYSTEMKEYUP, can't tell you about Ctrl...

Categories

Resources