Skip to content

Commit

Permalink
Merge pull request #61 from segrey/run-sendctrlc-with-java
Browse files Browse the repository at this point in the history
run sendctrlc.exe with Java (no console window, simpler code) (#60, #55)
  • Loading branch information
oleg-nenashev committed Dec 8, 2018
2 parents cbf2093 + be5a29f commit c9537f4
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 75 deletions.
5 changes: 0 additions & 5 deletions native/java-interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ JNIEXPORT jboolean JNICALL Java_org_jvnet_winp_Native_kill(JNIEnv* env, jclass c
return KillProcessEx(pid, recursive);
}

JNIEXPORT jboolean JNICALL Java_org_jvnet_winp_Native_sendCtrlC(JNIEnv* env, jclass clazz, jint pid, jstring sendctrlcExePath) {
const wchar_t* exePath = (wchar_t*)env->GetStringChars(sendctrlcExePath, NULL);
return SendCtrlC(env, clazz, pid, exePath);
}

JNIEXPORT jint JNICALL Java_org_jvnet_winp_Native_setPriority(JNIEnv* env, jclass clazz, jint pid, jint priority) {
auto_handle hProcess = OpenProcess(PROCESS_SET_INFORMATION, FALSE, pid);
if(hProcess && SetPriorityClass(hProcess, priority)) {
Expand Down
8 changes: 0 additions & 8 deletions native/java-interface.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 0 additions & 57 deletions native/winp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,63 +13,6 @@
// This file uses a long buffer, because it prints executable paths in some cases.
#define ERRMSG_SIZE 512

//---------------------------------------------------------------------------
// SendCtrlC
//
// Sends CTRL+C to the specified process.
//
// Parameters:
// dwProcessId - identifier of the process to terminate
//
// Returns:
// TRUE, if successful, FALSE - otherwise.
// When used from JNI, exceptions may be thrown instead
//
BOOL WINAPI SendCtrlC(JNIEnv* pEnv, jclass clazz, IN DWORD dwProcessId, const wchar_t* pExePath) {
char errorBuffer[ERRMSG_SIZE];
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

std::wstring exepath(pExePath);
std::wstring cmd = L'"' + exepath + L"\" " + std::to_wstring(dwProcessId);
std::vector<wchar_t> cmd_buffer(cmd.begin(), cmd.end()); // with C++17, could just use cmd.data()

BOOL started = CreateProcessW(NULL, &cmd_buffer[0], NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi);

BOOL success = FALSE;
if (started) {
// wait for termination if the process started, max. 5 secs
DWORD ret = WaitForSingleObject(pi.hProcess, 5000);
if (ret != WAIT_OBJECT_0) {
sprintf_s<ERRMSG_SIZE>(errorBuffer, "Failed to send Ctrl+C to process with pid=%d. WaitForSingleObject exit code: %d (last error: d).", dwProcessId, ret, GetLastError());
reportError(pEnv, errorBuffer);
}

// then set success flag if the exit code was 0
DWORD exit_code;
if (GetExitCodeProcess(pi.hProcess, &exit_code) != FALSE) {
success = (exit_code == 0);
if (exit_code != 0) {
sprintf_s<ERRMSG_SIZE>(errorBuffer, "External Ctrl+C execution failed for process pid=%d. Ctrl+C process exited with code %d: %s.", dwProcessId, exit_code,
(exit_code == -1) ? "Wrong arguments" : "Failed to attach to the console (see the AttachConsole WinAPI call)");
reportError(pEnv, errorBuffer);
}
}
} else {
sprintf_s<ERRMSG_SIZE>(errorBuffer, "Failed to send Ctrl+C to process with pid=%d. Signal process did not start: %s.", dwProcessId, cmd);
reportError(pEnv, errorBuffer);
}

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return success;
}

//---------------------------------------------------------------------------
// KillProcess
//
Expand Down
2 changes: 0 additions & 2 deletions native/winp.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#define reportError(env,msg) error(env,__FILE__,__LINE__,msg);
void error(JNIEnv* env, const char* file, int line, const char* msg);

BOOL WINAPI SendCtrlC(JNIEnv* pEnv, jclass clazz, IN DWORD dwProcessId, const wchar_t* pExePath);

//
// Kernel32.dll
//
Expand Down
106 changes: 106 additions & 0 deletions src/main/java/org/jvnet/winp/CtrlCSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.jvnet.winp;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.Locale;

class CtrlCSender {

private static final int TIMEOUT_MILLIS = 5000;

static boolean sendCtrlC(int pid, String ctrlCExePath) {
ProcessBuilder builder = new ProcessBuilder(ctrlCExePath, String.valueOf(pid));
builder.redirectErrorStream(true);
Process process;
try {
process = builder.start();
} catch (IOException e) {
throw new WinpException(e);
}
StreamGobbler stdout = new StreamGobbler(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()));
Integer exitCode = null;
try {
exitCode = waitFor(process);
} catch (InterruptedException ignored) {
}
stdout.stop();
if (exitCode == null) {
process.destroy();
throw new WinpException("Failed to send Ctrl+C to " + pid + ": " + TIMEOUT_MILLIS + " ms timeout exceeded");
}
if (exitCode == 0) {
return true;
}
throw new WinpException("Failed to send Ctrl+C, " + new File(ctrlCExePath).getName() +
" terminated with exit code " + stringifyExitCode(exitCode) + ", output: " + stdout.getText());
}

private static String stringifyExitCode(int exitCode) {
if (exitCode >= 0xC0000000 && exitCode < 0xD0000000) {
// http://support.microsoft.com/kb/308558:
// If the result code has the "C0000XXX" format, the task did not complete successfully (the "C" indicates an error condition).
// The most common "C" error code is "0xC000013A: The application terminated as a result of a CTRL+C".
return exitCode + " (0x" + Integer.toHexString(exitCode).toUpperCase(Locale.ENGLISH) + ")";
}
return String.valueOf(exitCode);
}

private static Integer waitFor(Process process) throws InterruptedException {
long endTime = System.currentTimeMillis() + TIMEOUT_MILLIS;
int i = 0;
do {
try {
return process.exitValue();
}
catch (IllegalThreadStateException ignore) {
Thread.sleep(i++ < 3 ? 10 : i < 5 ? 30 : 100);
}
} while (System.currentTimeMillis() < endTime);
return null;
}

private static class StreamGobbler implements Runnable {

private final Reader reader;
private final StringBuilder myBuffer = new StringBuilder();
private final Thread thread;
private boolean isStopped = false;

private StreamGobbler(Reader reader) {
this.reader = reader;
this.thread = new Thread(this, "sendctrlc.exe output reader");
this.thread.start();
}

public void run() {
char[] buf = new char[8192];
try {
int readCount;
while (!isStopped && (readCount = reader.read(buf)) >= 0) {
myBuffer.append(buf, 0, readCount);
}
if (isStopped) {
myBuffer.append("Failed to read output: force stopped");
}
}
catch (Exception e) {
myBuffer.append("Failed to read output: ").append(e.getClass().getName()).append(" raised");
}
}

private void stop() {
try {
this.thread.join(1000); // await to read whole buffered output
} catch (InterruptedException ignored) {
}
this.isStopped = true;
}

private String getText() {
return myBuffer.toString();
}
}
}
5 changes: 2 additions & 3 deletions src/main/java/org/jvnet/winp/Native.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class Native {
public static final String CTRLCEXE_NAME = "64".equals(System.getProperty("sun.arch.data.model")) ? "sendctrlc.x64" : "sendctrlc";

native static boolean kill(int pid, boolean recursive);
native static boolean sendCtrlC(int pid, String sendctrlcExePath);
native static boolean isCriticalProcess(int pid);
native static boolean isProcessRunning(int pid);
native static int setPriority(int pid, int value);
Expand Down Expand Up @@ -75,7 +74,7 @@ class Native {

/**
* Sends Ctrl+C to the process.
* Due to the Windows platform specifics, this execution will spawn a separate thread to deliver the signal.
* Due to the Windows platform specifics, this execution will spawn a separate process to deliver the signal.
* This process is expected to be executed within a 5-second timeout.
* @param pid PID to receive the signal
* @return {@code true} if the signal was delivered successfully
Expand All @@ -87,7 +86,7 @@ public static boolean sendCtrlC(int pid) throws WinpException {
LOGGER.log(Level.WARNING, "Cannot send the CtrlC signal to the process. Cannot find the executable {0}.dll", CTRLCEXE_NAME);
return false;
}
return sendCtrlC(pid, ctrlCExePath);
return CtrlCSender.sendCtrlC(pid, ctrlCExePath);
}

static {
Expand Down

0 comments on commit c9537f4

Please sign in to comment.