私は(過去に)クロスプラットフォーム(Windows / Unix)アプリケーションを作成しました。これは、コマンドラインから起動すると、ユーザーが入力した組み合わせを同じ方法で処理しますCtrl(Cつまり、アプリケーションをクリーンに終了します)。
Windowsで、別の(無関係の)プロセスからのプロセスに相当するCtrl- / SIGINT /を送信して、クリーンに終了するように要求することはできますか(リソースを整理する機会を与えるなど)?C
このトピックについていくつか調査を行ったところ、予想以上に人気があることが判明しました。KindDragon の返信は重要なポイントの 1 つでした。
このトピックに関する長いブログ投稿を書き、動作するデモ プログラムを作成しました。これは、このタイプのシステムを使用してコマンド ライン アプリケーションをいくつかの優れた方法で閉じる方法を示しています。その投稿には、私が調査で使用した外部リンクもリストされています。
つまり、これらのデモ プログラムは次のことを行います。
pinvoke
、6 秒間実行し、 で表示し、 .Netpinvoke
で停止します。ConsoleCtrlEvent
編集:ここでコードに興味がある人のための@KindDragonからの修正されたソリューション。CTRL最初のプログラムを停止した後に他のプログラムを開始する予定がある場合は、 +処理を再度有効にする必要があります。そうしないと、次のプロセスが親の無効状態を継承し、 +Cに応答しなくなります。CTRLC
[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){}
}
私が解決策に最も近いのは、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 が押されたかのように評価されます。
良い点は、ターゲット プロセスのみが影響を受け、任意のプロセス (ウィンドウ化されたプロセスであっても) をターゲットにできることです。欠点の 1 つは、kernel32!CtrlRoutine のアドレスを検出するために ctrl-break イベントを送信するとアプリが強制終了されるため、私の小さなアプリをバッチ ファイルで使用できないことです。
(start
バッチ ファイルで実行する場合は、前に を付けます。)
私はこの質問に少し遅れていると思いますが、とにかく同じ問題を抱えている人のために何かを書きます. これは、私がこの質問に与えたのと同じ答えです。
私の問題は、アプリケーションを GUI アプリケーションにしたいのですが、実行されるプロセスは、対話型のコンソール ウィンドウを接続せずにバックグラウンドで実行する必要があることでした。このソリューションは、親プロセスがコンソール プロセスの場合にも機能すると思います。ただし、「CREATE_NO_WINDOW」フラグを削除する必要がある場合があります。
ラッパーアプリでGenerateConsoleCtrlEvent () を使用してこれを解決することができました。トリッキーな部分は、ドキュメントが正確な使用方法と落とし穴について明確ではないことです.
私の解決策は、ここで説明されている内容に基づいています。しかし、それはすべての詳細を実際に説明したわけではなく、エラーが発生したため、これを機能させる方法の詳細を次に示します。
新しいヘルパー アプリケーション「Helper.exe」を作成します。このアプリケーションは、アプリケーション (親) と閉じたい子プロセスの間に位置します。また、実際の子プロセスも作成されます。この「仲介者」プロセスが必要です。そうしないと、GenerateConsoleCtrlEvent() が失敗します。
ある種の IPC メカニズムを使用して、ヘルパーが子プロセスを閉じる必要があることを親プロセスからヘルパー プロセスに伝達します。ヘルパーがこのイベントを取得すると、それ自体と子プロセスを閉じる「GenerateConsoleCtrlEvent(CTRL_BREAK, 0)」が呼び出されます。子プロセスをキャンセルしたいときに親が完了するイベントオブジェクトを自分で使用しました。
Helper.exe を作成するには、CREATE_NO_WINDOW と CREATE_NEW_PROCESS_GROUP を使用して作成します。子プロセスを作成するときは、フラグなし (0) で作成します。これは、親からコンソールを派生させることを意味します。これを行わないと、イベントが無視されます。
各ステップをこのように行うことが非常に重要です。いろいろな組み合わせを試してきましたが、この組み合わせしかありません。CTRL_C イベントを送信することはできません。成功を返しますが、プロセスによって無視されます。機能するのは CTRL_BREAK だけです。どちらも最後に ExitProcess() を呼び出すため、特に問題はありません。
また、子プロセス ID のプロセス グループ ID を使用して GenerateConsoleCtrlEvent() を呼び出すこともできず、ヘルパー プロセスが存続し続けます。これも失敗します。
これを機能させるために丸一日を費やしました。この解決策は私にとってはうまくいきますが、他に何か追加することがあればしてください。私はネット上で同様の問題を抱えている人をたくさん見つけましたが、問題に対する明確な解決策はありませんでした. GenerateConsoleCtrlEvent() の仕組みも少し奇妙ですので、詳細を知っている人は共有してください。
別のプロセスに対して呼び出すと、どういうわけかGenerateConsoleCtrlEvent()
エラーが返されますが、別のコンソール アプリケーションにアタッチして、すべての子プロセスにイベントを送信することはできます。
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
}
編集:
GUIアプリの場合、Windows開発でこれを処理する「通常の」方法は、プロセスのメインウィンドウにWM_CLOSEメッセージを送信することです。
コンソールアプリの場合、SetConsoleCtrlHandlerを使用してを追加する必要がありますCTRL_C_EVENT
。
アプリケーションがそれを尊重しない場合は、TerminateProcessを呼び出すことができます。
C++ アプリで使用するコードを次に示します。
良い点 :
マイナス点 :
// 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;
}
使用例:
DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
cout << "Signal sent" << endl;
}
現時点ではそうではないため、明確にする必要があります。 Ctrl-C を送信するように修正およびコンパイルされたバージョンの SendSignal があります(デフォルトでは、Ctrl+Break のみを送信します)。ここにいくつかのバイナリがあります:
(2014-3-7) : Ctrl-C を使用して 32 ビット バージョンと 64 ビット バージョンの両方をビルドしました。SendSignalCtrlC.exe という名前で、 https ://dl.dropboxusercontent.com/u/49065779/ からダウンロードできます。 sendsignalctrlc/x86/SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe -- Juraj Michalak
念のため、これらのファイルもミラーリングしました: 32
ビット
バージョン: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0 /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0
免責事項: 私はこれらのファイルをビルドしませんでした。コンパイルされた元のファイルは変更されていません。テストされた唯一のプラットフォームは 64 ビットの Windows 7 です。
私はこれが複雑すぎることに気付き、回避策としてSendKeysCTRLを使用して-Cキーストロークをコマンド ライン ウィンドウ (つまり cmd.exe ウィンドウ)に送信しました。