code

Windows의 응용 프로그램에 Ctrl-C (SIGINT)를 보낼 수 있습니까?

codestyles 2020. 9. 25. 07:54
반응형

Windows의 응용 프로그램에 Ctrl-C (SIGINT)를 보낼 수 있습니까?


나는 (과거에), 명령 줄에서 시작했을 때, 사용자 입력 처리 크로스 플랫폼 (윈도우 / 유닉스) 응용 프로그램을 작성했습니다 Ctrl- C(즉 깨끗하게 응용 프로그램을 종료) 같은 방법으로 조합.

Windows에서 Ctrl- C/ SIGINT / 다른 (관련되지 않은) 프로세스의 프로세스와 동등한 프로세스 를 보내 완전히 종료되도록 요청할 수 있습니까 (리소스 정리 등의 기회 제공)?


제가 가장 가까운 해결책은 SendSignal 타사 앱입니다. 작성자는 소스 코드와 실행 파일을 나열합니다. 64 비트 창 (32 비트 프로그램으로 실행, 다른 32 비트 프로그램 종료)에서 작동하는지 확인했지만 코드를 Windows 프로그램에 삽입하는 방법을 파악하지 못했습니다 (32 비트 또는 64 비트).

작동 원리 :

디버거를 자세히 살펴본 후 ctrl-break와 같은 신호와 관련된 동작을 실제로 수행하는 진입 점이 kernel32! CtrlRoutine이라는 것을 발견했습니다. 이 함수는 ThreadProc와 동일한 프로토 타입을 가지고 있으므로 코드를 삽입하지 않고도 CreateRemoteThread와 함께 직접 사용할 수 있습니다. 그러나 이것은 내 보낸 심볼이 아닙니다! 다른 버전의 Windows에서 다른 주소에 있으며 이름도 다릅니다. 무엇을해야합니까?

내가 마침내 생각해 낸 해결책은 다음과 같습니다. 내 앱에 콘솔 ctrl 핸들러를 설치 한 다음 앱에 대한 ctrl-break 신호를 생성합니다. 핸들러가 호출되면 스택의 맨 위를 돌아보고 kernel32! BaseThreadStart에 전달 된 매개 변수를 찾습니다. 첫 번째 매개 변수는 원하는 스레드의 시작 주소 인 kernel32! CtrlRoutine의 주소입니다. 그런 다음 핸들러에서 돌아와서 신호를 처리했으며 앱을 종료해서는 안된다는 것을 나타냅니다. 메인 스레드로 돌아가서 kernel32! CtrlRoutine의 주소가 검색 될 때까지 기다립니다. 일단 가져 오면 발견 된 시작 주소로 대상 프로세스에 원격 스레드를 만듭니다. 이로 인해 대상 프로세스의 ctrl 처리기가 마치 ctrl-break가 눌린 것처럼 평가됩니다!

좋은 점은 대상 프로세스 만 영향을 받고 모든 프로세스 (윈도우 프로세스 포함)를 대상으로 지정할 수 있다는 것입니다. 한 가지 단점은 내 작은 앱을 배치 파일에서 사용할 수 없다는 것입니다. kernel32! CtrlRoutine의 주소를 찾기 위해 ctrl-break 이벤트를 보낼 때 앱을 죽이기 때문입니다.

( start배치 파일에서 실행하는 경우 먼저 사용하십시오.)


이 주제에 대해 조사를 해봤는데 예상보다 더 인기가 많았습니다. KindDragon의 답변은 핵심 포인트 중 하나였습니다.

나는 주제에 대해 더 긴 블로그 게시물 을 작성하고 작동하는 데모 프로그램을 만들었습니다.이 프로그램은 이러한 유형의 시스템을 사용하여 몇 가지 멋진 방식으로 명령 줄 응용 프로그램을 닫는 것을 보여줍니다. 그 게시물은 또한 내가 연구에 사용한 외부 링크를 나열합니다.

요컨대, 이러한 데모 프로그램은 다음을 수행합니다.

  • .Net을 사용하여 보이는 창으로 프로그램을 시작하고, pinvoke로 숨기기, 6 초 동안 실행, pinvoke로 표시, .Net으로 중지.
  • .Net을 사용하여 창없이 프로그램을 시작하고 6 초 동안 실행 한 다음 콘솔을 연결하고 ConsoleCtrlEvent를 실행하여 중지합니다.

편집 : 현재 코드에 관심이있는 사람들을위한 KindDragon의 수정 된 솔루션입니다. 첫 번째 프로그램을 중지 한 후 다른 프로그램을 시작하려는 경우 Ctrl-C 처리를 다시 활성화해야합니다. 그렇지 않으면 다음 프로세스가 부모의 비활성화 된 상태를 상속하고 Ctrl-C에 응답하지 않습니다.

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);

delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
  CTRL_C_EVENT = 0,
  CTRL_BREAK_EVENT,
  CTRL_CLOSE_EVENT,
  CTRL_LOGOFF_EVENT = 5,
  CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public void StopProgram(Process proc)
{
  //This does not require the console window to be visible.
  if (AttachConsole((uint)proc.Id))
  {
    // Disable Ctrl-C handling for our program
    SetConsoleCtrlHandler(null, true); 
    GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

    //Moved this command up on suggestion from Timothy Jannace (see comments below)
    FreeConsole();

    // Must wait here. If we don't and re-enable Ctrl-C
    // handling below too fast, we might terminate ourselves.
    proc.WaitForExit(2000);

    //Re-enable Ctrl-C handling or any subsequently started
    //programs will inherit the disabled state.
    SetConsoleCtrlHandler(null, false); 
  }
}

또한 AttachConsole()전송 된 신호가 실패 할 경우 비상 솔루션을 계획 하십시오.

if (!proc.HasExited)
{
  try
  {
    proc.Kill();
  }
  catch (InvalidOperationException e){}
}

I guess I'm a bit late on this question but I'll write something anyway for anyone having the same problem. This is the same answer as I gave to this question.

My problem was that I'd like my application to be a GUI application but the processes executed should be run in the background without any interactive console window attached. I think this solution should also work when the parent process is a console process. You may have to remove the "CREATE_NO_WINDOW" flag though.

I managed to solve this using GenerateConsoleCtrlEvent() with a wrapper app. The tricky part is just that the documentation is not really clear on exactly how it can be used and the pitfalls with it.

My solution is based on what is described here. But that didn't really explain all the details either and with an error, so here is the details on how to get it working.

Create a new helper application "Helper.exe". This application will sit between your application (parent) and the child process you want to be able to close. It will also create the actual child process. You must have this "middle man" process or GenerateConsoleCtrlEvent() will fail.

Use some kind of IPC mechanism to communicate from the parent to the helper process that the helper should close the child process. When the helper get this event it calls "GenerateConsoleCtrlEvent(CTRL_BREAK, 0)" which closes down itself and the child process. I used an event object for this myself which the parent completes when it wants to cancel the child process.

To create your Helper.exe create it with CREATE_NO_WINDOW and CREATE_NEW_PROCESS_GROUP. And when creating the child process create it with no flags (0) meaning it will derive the console from its parent. Failing to do this will cause it to ignore the event.

It is very important that each step is done like this. I've been trying all different kinds of combinations but this combination is the only one that works. You can't send a CTRL_C event. It will return success but will be ignored by the process. CTRL_BREAK is the only one that works. Doesn't really matter since they will both call ExitProcess() in the end.

You also can't call GenerateConsoleCtrlEvent() with a process groupd id of the child process id directly allowing the helper process to continue living. This will fail as well.

I spent a whole day trying to get this working. This solution works for me but if anyone has anything else to add please do. I went all over the net finding lots of people with similar problems but no definite solution to the problem. How GenerateConsoleCtrlEvent() works is also a bit weird so if anyone knows more details on it please share.


Edit:

For a GUI App, the "normal" way to handle this in Windows development would be to send a WM_CLOSE message to the process's main window.

For a console app, you need to use SetConsoleCtrlHandler to add a CTRL_C_EVENT.

If the application doesn't honor that, you could call TerminateProcess.


Somehow GenerateConsoleCtrlEvent() return error if you call it for another process, but you can attach to another console application and send event to all child processes.

void SendControlC(int pid)
{
    AttachConsole(pid); // attach to process console
    SetConsoleCtrlHandler(NULL, TRUE); // disable Control+C handling for our app
    GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // generate Control+C event
}

Here is the code I use in my C++ app.

Positive points :

  • Works from console app
  • Works from Windows service
  • No delay required
  • Does not close the current app

Negative points :

  • The main console is lost and a new one is created (see FreeConsole)
  • The console switching give strange results...

// Inspired from http://stackoverflow.com/a/15281070/1529139
// and http://stackoverflow.com/q/40059902/1529139
bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
{
    bool success = false;
    DWORD thisConsoleId = GetCurrentProcessId();
    // Leave current console if it exists
    // (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
    bool consoleDetached = (FreeConsole() != FALSE);

    if (AttachConsole(dwProcessId) != FALSE)
    {
        // Add a fake Ctrl-C handler for avoid instant kill is this console
        // WARNING: do not revert it or current program will be also killed
        SetConsoleCtrlHandler(nullptr, true);
        success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
        FreeConsole();
    }

    if (consoleDetached)
    {
        // Create a new console if previous was deleted by OS
        if (AttachConsole(thisConsoleId) == FALSE)
        {
            int errorCode = GetLastError();
            if (errorCode == 31) // 31=ERROR_GEN_FAILURE
            {
                AllocConsole();
            }
        }
    }
    return success;
}

Usage example :

DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
    cout << "Signal sent" << endl;
}

        void SendSIGINT( HANDLE hProcess )
        {
            DWORD pid = GetProcessId(hProcess);
            FreeConsole();
            if (AttachConsole(pid))
            {
                // Disable Ctrl-C handling for our program
                SetConsoleCtrlHandler(NULL, true);

                GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // SIGINT

                //Re-enable Ctrl-C handling or any subsequently started
                //programs will inherit the disabled state.
                SetConsoleCtrlHandler(NULL, false);

                WaitForSingleObject(hProcess, 10000);
            }
        }

It should be made crystal clear because at the moment it isn't. There is a modified and compiled version of SendSignal to send Ctrl-C (by default it only sends Ctrl+Break). Here are some binaries:

(2014-3-7) : I built both 32-bit and 64-bit version with Ctrl-C, it's called SendSignalCtrlC.exe and you can download it at: https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe -- Juraj Michalak

I have also mirrored those files just in case:
32-bit version: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64-bit version: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Disclaimer: I didn't build those files. No modification was made to the compiled original files. The only platform tested is the 64-bit Windows 7. It is recommended to adapt the source available at http://www.latenighthacking.com/projects/2003/sendSignal/ and compile it yourself.


In Java, using JNA with the Kernel32.dll library, similar to a C++ solution. Runs the CtrlCSender main method as a Process which just gets the console of the process to send the Ctrl+C event to and generates the event. As it runs separately without a console the Ctrl+C event does not need to be disabled and enabled again.

CtrlCSender.java - Based on Nemo1024's and KindDragon's answers.

Given a known process ID, this consoless application will attach the console of targeted process and generate a CTRL+C Event on it.

import com.sun.jna.platform.win32.Kernel32;    

public class CtrlCSender {

    public static void main(String args[]) {
        int processId = Integer.parseInt(args[0]);
        Kernel32.INSTANCE.AttachConsole(processId);
        Kernel32.INSTANCE.GenerateConsoleCtrlEvent(Kernel32.CTRL_C_EVENT, 0);
    }
}

Main Application - Runs CtrlCSender as a separate consoless process

ProcessBuilder pb = new ProcessBuilder();
pb.command("javaw", "-cp", System.getProperty("java.class.path", "."), CtrlCSender.class.getName(), processId);
pb.redirectErrorStream();
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process ctrlCProcess = pb.start();
ctrlCProcess.waitFor();

Yes. The windows-kill project does exactly what you want:

windows-kill -SIGINT 1234

A friend of mine suggested a complete different way of solving the problem and it worked for me. Use a vbscript like below. It starts and application, let it run for 7 seconds and close it using ctrl+c.

'VBScript Example

Set WshShell = WScript.CreateObject("WScript.Shell")

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

Based on process id, we can send the signal to process to terminate forcefully or gracefully or any other signal.

List all process :

C:\>tasklist

To kill the process:

C:\>Taskkill /IM firefox.exe /F
or
C:\>Taskkill /PID 26356 /F

Details:

http://tweaks.com/windows/39559/kill-processes-from-command-prompt/


I found all this too complicated and used SendKeys to send a CTRL-C keystroke to the command line window (i.e. cmd.exe window) as a workaround.

참고URL : https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows

반응형