Last active
March 5, 2018 14:03
-
-
Save mtfelian/9e34f8d6fa2d6c8fd468260ba4d5b508 to your computer and use it in GitHub Desktop.
Windows graceful process shutdown (WM_CLOSE / Ctrl-Break)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"os/exec" | |
"syscall" | |
"time" | |
"unsafe" | |
) | |
//noinspection GoSnakeCaseUsage | |
const ( | |
// WAIT_TIMEOUT means the time-out interval elapsed, and the object's state is nonsignaled. | |
WAIT_TIMEOUT = 0x00000102 | |
// WAIT_FAILED means the function WaitForSingleObject() has failed | |
WAIT_FAILED = 0xFFFFFFFF | |
// WM_CLOSE is as a signal that a window or an application should terminate. | |
WM_CLOSE = 16 | |
// SYNCHRONIZE is a right to use the object for synchronization. This enables a | |
// thread to wait until the object is in the signaled state. | |
SYNCHRONIZE = 0x00100000 | |
// CTRL_BREAK_EVENT is a CTRL+BREAK signal | |
CTRL_BREAK_EVENT = 1 | |
) | |
var ( | |
user32DLL = syscall.NewLazyDLL("user32.dll") | |
kernel32DLL = syscall.NewLazyDLL("kernel32.dll") | |
procEnumWindows = user32DLL.NewProc("EnumWindows") | |
procPostMessage = user32DLL.NewProc("PostMessageW") | |
procGetWindowThreadProcessId = user32DLL.NewProc("GetWindowThreadProcessId") | |
procWaitForSingleObject = kernel32DLL.NewProc("WaitForSingleObject") | |
procOpenProcess = kernel32DLL.NewProc("OpenProcess") | |
procCloseHandle = kernel32DLL.NewProc("CloseHandle") | |
procGenerateConsoleCtrlEvent = kernel32DLL.NewProc("GenerateConsoleCtrlEvent") | |
) | |
// GenerateConsoleCtrlEvent dwCtrlEvent for the process group with ID dwProcessGroupId | |
func GenerateConsoleCtrlEvent(dwCtrlEvent, dwProcessGroupId uintptr) error { | |
_, _, err := procGenerateConsoleCtrlEvent.Call(dwCtrlEvent, dwProcessGroupId) | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return err | |
} | |
// EnumWindows with callback func pointer lpEnumFunc and user-given param lParam | |
func EnumWindows(lpEnumFunc, lParam uintptr) (bool, error) { | |
r1, _, err := procEnumWindows.Call(lpEnumFunc, lParam) | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return r1 != 0, err | |
} | |
// PostMessage Msg to windows handle hWnd, wParam and lParam is not used | |
func PostMessage(hWnd, Msg, wParam, lParam uintptr) (bool, error) { | |
r1, _, err := procPostMessage.Call(hWnd, Msg, wParam, lParam) | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return r1 != 0, err | |
} | |
// GetWindowThreadProcessId for window with handle hWnd | |
// Returns: ID of the thread that created the window, PID (ID of the process that created the window) | |
func GetWindowThreadProcessId(hWnd uintptr) (uintptr, int, error) { | |
var lpdwProcessId int | |
r1, _, err := procGetWindowThreadProcessId.Call(hWnd, uintptr(unsafe.Pointer(&lpdwProcessId))) | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return r1, lpdwProcessId, err | |
} | |
// OpenProcess with given access rights and dwProcessId | |
func OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId uintptr) (uintptr, error) { | |
r1, _, err := procOpenProcess.Call(dwDesiredAccess, bInheritHandle, dwProcessId) | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return r1, err | |
} | |
// CloseHandle hObject | |
func CloseHandle(hObject uintptr) (bool, error) { | |
r1, _, err := procCloseHandle.Call(hObject) | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return r1 != 0, err | |
} | |
// WaitForSingleObject until the specified object with hHandle is in the signaled state or | |
// the time-out interval dwMilliseconds elapses | |
func WaitForSingleObject(hHandle, dwMilliseconds uintptr) (uintptr, error) { | |
r1, _, err := procWaitForSingleObject.Call(hHandle, dwMilliseconds) | |
if r1 == WAIT_FAILED { | |
if err != nil && err == syscall.Errno(0) { | |
err = nil | |
} | |
return r1, err | |
} | |
return r1, nil | |
} | |
func main() { | |
const ( | |
windowTimeout = 15000 | |
killTimeout uintptr = 3000 | |
) | |
cmdLine := `winver` | |
cmd := exec.Command(cmdLine) | |
fmt.Println("cmd.Start()") | |
if err := cmd.Start(); err != nil { | |
fmt.Println("cmd.Start():", err) | |
return | |
} | |
fmt.Println("cmd.Process.Pid:", cmd.Process.Pid) | |
fmt.Println("waiting for window...") | |
var found bool | |
var windowHandle uintptr | |
for i := 0; i < windowTimeout/100 && !found; i++ { | |
time.Sleep(100 * time.Millisecond) // wait for a window to draw | |
EnumWindows(syscall.NewCallback(func(hWnd, lParam uintptr) uintptr { | |
// hWnd is a current iteration windows process handle | |
// lParam we passing here (into callback) from EnumWindows() call to match window's PID with it | |
_, pid, err := GetWindowThreadProcessId(hWnd) | |
if err != nil { | |
fmt.Println("[ERR] GetWindowThreadProcessId():", err) | |
} | |
if pid == int(lParam) { | |
fmt.Println("window initialized") | |
found, windowHandle = true, hWnd | |
return 0 // it is used to stop, so should not check the return value of EnumWindows() | |
} | |
return 1 // continue | |
}), uintptr(cmd.Process.Pid)) | |
} | |
if !found { | |
fmt.Println("[ERR] failed to find window at start") | |
} | |
fmt.Println("OpenProcess()") | |
processHandle, err := OpenProcess(SYNCHRONIZE, 0, uintptr(cmd.Process.Pid)) | |
if err != nil { | |
fmt.Println("[ERR] OpenProcess():", err) | |
} | |
fmt.Println("PostMessage()") | |
postOK, err := PostMessage(windowHandle, WM_CLOSE, 0, 0) | |
if err != nil || !postOK { | |
fmt.Println("[ERR] PostMessage():", err) | |
// try to send it the ctrl-break event; MSDN docs tells that we can't send ctrl-c to | |
// another process group | |
fmt.Println("GenerateConsoleCtrlEvent()") | |
if err := GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, uintptr(cmd.Process.Pid)); err != nil { | |
fmt.Println("[ERR] GenerateConsoleCtrlEvent():", err) | |
} | |
} | |
fmt.Println("WaitForSingleObject()") | |
waitResult, err := WaitForSingleObject(processHandle, killTimeout) | |
if err != nil { | |
fmt.Println("[ERR] WaitForSingleObject():", err) | |
} | |
closed, err := CloseHandle(processHandle) | |
if err != nil || !closed { | |
fmt.Println("[ERR] CloseHandle():", err) | |
} | |
if waitResult == WAIT_TIMEOUT { | |
fmt.Println("cmd.Process.Kill()") | |
if err := cmd.Process.Kill(); err != nil { | |
fmt.Println("[ERR] cmd.Process.Kill():", err) | |
} | |
} | |
fmt.Println("OK") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment