2009-05-01 29 views
70

Tôi có (trong quá khứ) các ứng dụng đa nền tảng (Windows/Unix) được viết, khi bắt đầu từ dòng lệnh, xử lý một người dùng nhập Ctrl - C kết hợp theo cùng một cách (tức là chấm dứt ứng dụng sạch).Tôi có thể gửi ctrl-C (SIGINT) đến một ứng dụng trên Windows không?

Có thể trên Windows để gửi một Ctrl - C/SIGINT/tương đương với một quá trình từ khác (không liên quan) quá trình để yêu cầu chấm dứt sạch (cho nó một cơ hội để dọn dẹp các nguồn lực, vv) ?

+0

tôi đã quan tâm đến việc gửi Ctrl-C để quá trình dịch vụ java chỉ để có được threaddumps. Có vẻ như 'jstack' có thể được sử dụng một cách đáng tin cậy thay cho vấn đề cụ thể này: https://stackoverflow.com/a/47723393/603516 – Vadzim

Trả lời

25

Gần nhất mà tôi đã đến với giải pháp là ứng dụng của bên thứ 3 SendSignal. Tác giả liệt kê mã nguồn và một tệp thực thi. Tôi đã xác minh rằng nó hoạt động dưới các cửa sổ 64-bit (chạy như một chương trình 32-bit, giết chết một chương trình 32-bit), nhưng tôi đã không tìm ra cách để nhúng mã vào một chương trình cửa sổ (hoặc 32-bit hoặc 64 bit).

Cách hoạt động:

Sau nhiều đào xung quanh trong trình gỡ lỗi tôi phát hiện ra rằng điểm mấu chốt mà thực sự hiện các hành vi liên quan đến một tín hiệu như ctrl-break là kernel32 CtrlRoutine!. Hàm này có nguyên mẫu giống như ThreadProc, vì vậy nó có thể được sử dụng trực tiếp với CreateRemoteThread mà không cần phải tiêm mã. Tuy nhiên, đó không phải là một biểu tượng xuất khẩu! Nó ở các địa chỉ khác nhau (và thậm chí có các tên khác nhau) trên các phiên bản Windows khác nhau. Phải làm gì?

Đây là giải pháp cuối cùng tôi đã đưa ra. Tôi cài đặt trình xử lý ctrl console cho ứng dụng của mình, sau đó tạo tín hiệu ctrl-break cho ứng dụng của tôi. Khi trình xử lý của tôi được gọi, tôi nhìn lại phía trên cùng của ngăn xếp để tìm ra các tham số được truyền cho kernel32! BaseThreadStart. Tôi lấy param đầu tiên, đó là địa chỉ bắt đầu mong muốn của thread, đó là địa chỉ của kernel32! CtrlRoutine. Sau đó, tôi trở về từ xử lý của tôi, chỉ ra rằng tôi đã xử lý tín hiệu và ứng dụng của tôi không nên chấm dứt. Quay trở lại chủ đề chính, tôi đợi cho đến khi địa chỉ của kernel32! CtrlRoutine đã được lấy ra. Một khi tôi đã có nó, tôi tạo ra một chủ đề từ xa trong quá trình mục tiêu với địa chỉ bắt đầu phát hiện. Điều này làm cho các trình xử lý ctrl trong quá trình đích được đánh giá như thể đã nhấn ctrl-break!

Điều tuyệt vời là chỉ có quy trình đích bị ảnh hưởng và bất kỳ quy trình nào (ngay cả quy trình cửa sổ) đều có thể được nhắm mục tiêu. Một nhược điểm là ứng dụng nhỏ của tôi không thể được sử dụng trong một tập tin batch, vì nó sẽ giết nó khi nó gửi sự kiện ctrl-break để khám phá địa chỉ của kernel32! CtrlRoutine.

(trước nó start nếu chạy nó trong một tập tin thực thi.)

+1

+1 - Mã được liên kết sử dụng Ctrl-Break thay vì Ctrl-C nhưng trên khuôn mặt của nó (xem tài liệu cho GenerateConsoleCtrlEvent) có thể được điều chỉnh cho Ctrl-C. –

+5

Có thực sự là một chức năng để làm điều này cho bạn, lol, "GenerateConsoleCtrlEvent". Xem: http://msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx Xem thêm trả lời của tôi ở đây: http://serverfault.com/a/501045/10023 – Triynko

+0

Liên kết Github liên quan đến câu trả lời ở trên: https://github.com/walware/statet/tree/master/de.walware.statet.r.console.core/win32 – meawoppl

6

Edit:

Đối với một giao diện ứng dụng, các "bình thường" cách để xử lý này trong phát triển Windows sẽ gửi một thông điệp WM_CLOSE đến cửa sổ chính của quá trình.

Đối với ứng dụng bảng điều khiển, bạn cần sử dụng SetConsoleCtrlHandler để thêm CTRL_C_EVENT.

Nếu ứng dụng không tôn trọng điều đó, bạn có thể gọi TerminateProcess.

+0

Tôi muốn làm điều này trong một ứng dụng dòng lệnh (không có cửa sổ). TerminateProcess là một cơ chế tiêu diệt lực (tương tự như SIGKILL) vì vậy không cung cấp cho ứng dụng chấm dứt bất kỳ cơ hội nào để dọn sạch. –

+0

@Matthew Murdoch: Đã cập nhật để bao gồm thông tin về cách xử lý điều này trong ứng dụng bảng điều khiển.Bạn cũng có thể nắm bắt các sự kiện khác, bao gồm tắt cửa sổ, hoặc bàn điều khiển đóng, vv, thông qua cùng một cơ chế. Xem MSDN để biết chi tiết đầy đủ. –

+0

@Reed Copsey: Nhưng tôi muốn bắt đầu quá trình này từ một quy trình riêng biệt. Tôi đã có một cái nhìn tại GenerateConsoleCtrlEvent (tham chiếu từ SetConsoleCtrlHandler) nhưng điều này dường như chỉ làm việc nếu quá trình được chấm dứt là trong cùng một nhóm quá trình '' như quá trình làm chấm dứt ... Bất kỳ ý tưởng? –

15

Tôi đoán Tôi hơi muộn về vấn đề này nhưng tôi sẽ viết một cái gì đó anyway cho bất cứ ai gặp vấn đề tương tự. Đây là câu trả lời giống như tôi đã đưa ra cho câu hỏi this.

Vấn đề của tôi là tôi muốn ứng dụng của mình là một ứng dụng GUI nhưng các quy trình được thực hiện sẽ chạy trong nền mà không cần bất kỳ cửa sổ bảng điều khiển tương tác nào được đính kèm. Tôi nghĩ rằng giải pháp này cũng nên làm việc khi quá trình cha mẹ là một quá trình giao diện điều khiển. Bạn có thể phải xóa cờ "CREATE_NO_WINDOW".

Tôi đã giải quyết được điều này bằng cách sử dụng GenerateConsoleCtrlEvent() với ứng dụng trình bao bọc. Phần khó khăn chỉ là tài liệu không thực sự rõ ràng về cách sử dụng nó và những cạm bẫy với nó.

Giải pháp của tôi dựa trên những gì được mô tả here. Nhưng điều đó đã không thực sự giải thích tất cả các chi tiết hoặc với một lỗi, do đó, đây là chi tiết về cách làm cho nó hoạt động.

Tạo ứng dụng trợ giúp mới "Helper.exe". Ứng dụng này sẽ nằm giữa ứng dụng của bạn (cha mẹ) và quá trình con bạn muốn có thể đóng. Nó cũng sẽ tạo ra quá trình con thực tế. Bạn phải có quá trình "trung gian" này hoặc GenerateConsoleCtrlEvent() sẽ thất bại.

Sử dụng một số loại cơ chế IPC để liên lạc từ cha mẹ với quy trình trợ giúp mà người trợ giúp phải đóng quá trình con. Khi người trợ giúp nhận được sự kiện này, nó gọi là "GenerateConsoleCtrlEvent (CTRL_BREAK, 0)" sẽ tự đóng và quá trình con. Tôi đã sử dụng một đối tượng sự kiện cho chính bản thân mình mà cha mẹ hoàn thành khi nó muốn hủy quá trình con.

Để tạo Helper.exe của bạn, hãy tạo nó bằng CREATE_NO_WINDOW và CREATE_NEW_PROCESS_GROUP. Và khi tạo quá trình con tạo ra nó không có cờ (0) có nghĩa là nó sẽ lấy được giao diện điều khiển từ cha mẹ của nó. Không thực hiện được điều này sẽ khiến nó bỏ qua sự kiện.

Điều quan trọng là mỗi bước được thực hiện như thế này. Tôi đã thử tất cả các loại kết hợp khác nhau nhưng sự kết hợp này là sự kết hợp duy nhất hoạt động. Bạn không thể gửi sự kiện CTRL_C. Nó sẽ trả về thành công nhưng sẽ bị bỏ qua bởi quá trình. CTRL_BREAK là người duy nhất hoạt động. Không thực sự quan trọng vì cả hai sẽ gọi ExitProcess() cuối cùng.

Bạn cũng không thể gọi GenerateConsoleCtrlEvent() với id nhóm processd của id tiến trình con trực tiếp cho phép tiến trình trợ giúp tiếp tục sống. Điều này cũng sẽ thất bại.

Tôi đã dành cả ngày để làm việc này. Giải pháp này làm việc cho tôi nhưng nếu có ai khác có thể thêm vào thì hãy làm. Tôi đã đi khắp nơi trên mạng tìm kiếm rất nhiều người có vấn đề tương tự nhưng không có giải pháp xác định cho vấn đề. Làm thế nào các công trình GenerateConsoleCtrlEvent() cũng có một chút lạ nên nếu có ai biết thêm chi tiết về nó, hãy chia sẻ.

+3

Cảm ơn bạn đã giải pháp! Đây là một ví dụ khác của Windows API là một mớ hỗn độn khủng khiếp. – vitaut

6

Lỗi nào đó nếu bạn gọi nó cho một quy trình khác, nhưng bạn có thể đính kèm vào một ứng dụng bảng điều khiển khác và gửi sự kiện đến tất cả các quy trình con.

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 
} 
+0

và cách trả lại quyền kiểm soát? và sau đó tôi gửi chương trình stop-c, stopar, nhưng tôi không thể làm gì bằng tay. –

+0

Nơi để kiểm soát trở lại? – KindDragon

+0

Tôi nghĩ rằng bạn gọi SetConsoleCtrlHandler (NULL, FALSE) để loại bỏ trình xử lý của bạn, xem các phản ứng khác nhau. – rogerdpack

43

Tôi đã thực hiện một số nghiên cứu về chủ đề này, hóa ra phổ biến hơn tôi dự đoán. Câu trả lời của KindDragon là một trong những điểm then chốt.

Tôi đã viết một longer blog post về chủ đề và tạo một chương trình demo làm việc, thể hiện bằng cách sử dụng loại hệ thống này để đóng ứng dụng dòng lệnh trong một vài thời trang tốt đẹp. Bài đăng đó cũng liệt kê các liên kết bên ngoài mà tôi đã sử dụng trong nghiên cứu của mình.

Nói tóm lại, những chương trình demo làm như sau:

  • Bắt đầu một chương trình với một cửa sổ có thể nhìn thấy sử dụng Net, ẩn với pinvoke, chạy trong 6 giây, hiển thị với pinvoke, dừng lại với Net.
  • Bắt đầu một chương trình mà không có một cửa sổ sử dụng Net, chạy trong 6 giây, dừng lại bằng cách gắn console và phát hành ConsoleCtrlEvent

Edit: Các giải pháp sửa đổi từ KindDragon cho những ai quan tâm đến mã ở đây và hiện nay. Nếu bạn dự định bắt đầu các chương trình khác sau khi dừng chương trình đầu tiên, bạn nên bật lại tính năng xử lý Ctrl-C, nếu không quy trình tiếp theo sẽ kế thừa trạng thái bị vô hiệu hóa của phụ huynh và sẽ không phản hồi lại Ctrl-C.

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

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

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

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

    FreeConsole(); 

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

Ngoài ra, kế hoạch cho một giải pháp dự phòng nếu AttachConsole() hoặc tín hiệu gửi nên thất bại, ví dụ ngủ thì đây:

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

Đây phải là câu trả lời được chấp nhận. – Triynko

+4

Lấy cảm hứng từ điều này, tôi đã tạo một ứng dụng cpp "độc lập" thực hiện: https://gist.github.com/rdp/f51fb274d69c5c31b6be trong trường hợp nó hữu ích. Và có, phương pháp này có thể gửi ctrl + c hoặc ctrl + break để "bất kỳ" pid, rõ ràng. – rogerdpack

+0

Thiếu một trong các DLL nhập khẩu "SetConsoleCtrlHandler", nhưng điều này không hoạt động. –

1

tôi thấy tất cả điều này quá phức tạp và sử dụng SendKeys để gửi một CTRL - C tổ hợp phím vào cửa sổ dòng lệnh (ví dụ: cửa sổ cmd.exe) làm giải pháp thay thế.

1

Một người bạn của tôi đề xuất một cách hoàn toàn khác nhau để giải quyết vấn đề và nó đã làm việc cho tôi. Sử dụng vbscript như dưới đây. Nó bắt đầu và ứng dụng, hãy để nó chạy trong 7 giây và đóng nó bằng cách sử dụng ctrl + c.

'VBScript Ví dụ

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

WshShell.Run "notepad.exe" 

WshShell.AppActivate "notepad" 

WScript.Sleep 7000 

WshShell.SendKeys "^C" 
+0

Điều này hoạt động nếu ứng dụng có một cửa sổ bạn có thể AppActive (mang đến nền trước). – rogerdpack

2

Nó nên được thực hiện tinh thể rõ ràng bởi vì vào lúc này nó không phải là. Có một phiên bản được sửa đổi và biên dịch của SendSignal để gửi Ctrl-C (theo mặc định nó chỉ gửi Ctrl + Break). Dưới đây là một số nhị phân:

(2014-3-7): tôi đã xây dựng cả hai phiên bản 32-bit và 64-bit với Ctrl-C, nó được gọi là SendSignalCtrlC.exe và bạn có thể tải nó tại địa chỉ: https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exehttps://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe - - Juraj Michalak

tôi cũng đã phản ánh các tập tin chỉ trong trường hợp:
32-bit phiên bản: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64-bit phiên bản: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Disclaimer: tôi không xây dựng những tập tin S. Không có sửa đổi nào được thực hiện đối với các tệp gốc đã biên soạn . Nền tảng duy nhất được thử nghiệm là Windows 64 bit 7. Bạn nên thích ứng với nguồn có sẵn tại http://www.latenighthacking.com/projects/2003/sendSignal/ và biên dịch nó cho chính mình.

2

Đây là mã tôi sử dụng trong ứng dụng C++ của mình.

điểm tích cực:

  • trình từ giao diện điều khiển ứng dụng
  • trình từ dịch vụ Windows
  • Không chậm trễ cần
  • Không đóng ứng dụng hiện tại

điểm Negative :

  • Giao diện điều khiển chính bị mất và một cái mới được tạo ra (xem FreeConsole)
  • Giao diện điều khiển chuyển đổi cho kết quả kỳ lạ ... ví dụ

// 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; 
} 

Cách sử dụng:

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

Dựa trên id quá trình, chúng tôi có thể gửi tín hiệu để xử lý để chấm dứt hiệu lực hoặc duyên dáng hoặc bất kỳ tín hiệu nào khác.

Liệt kê tất cả quá trình:

C:\>tasklist 

Để giết quá trình:

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

chi tiết:

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

2

Trong Java, sử dụng JNA với thư viện kernel32.dll, tương tự như giải pháp C++. Chạy phương thức main của CtrlCSender như là một Process mà chỉ nhận được console của tiến trình để gửi sự kiện Ctrl + C đến và tạo ra sự kiện. Khi nó chạy riêng mà không cần bàn điều khiển, sự kiện Ctrl + C không cần phải bị tắt và được bật lại.

CtrlCSender.java - Dựa trên Nemo1024'sKindDragon's câu trả lời.

Với một ID tiến trình đã biết, ứng dụng consoless này sẽ đính kèm giao diện điều khiển của quá trình được nhắm mục tiêu và tạo sự kiện CTRL + C trên đó.

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 - Chạy CtrlCSender như một quá trình consoless riêng

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(); 
1
 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); 
      } 
     } 
Các vấn đề liên quan