lxn / win の PR をマージしたやつに、Windows Service 関連の API を追加したので、Golang で Windows のサービスプログラムの開発がまぁまぁ簡単にできるようになった。
後の人のために以下に載せておきます。
main.go
package main
import (
"os"
"syscall"
"unsafe"
"winut"
"github.com/blono/win" // Fork of lxn/win see https://qiita.com/manymanyuni/items/c41f5bf0fd141e299336
)
func registerService() {
scManager := win.OpenSCManager(nil, nil, win.SC_MANAGER_CREATE_SERVICE|win.SC_MANAGER_LOCK)
if scManager == 0 {
return
}
service := win.CreateService(scManager, "my-service", "my-service",
win.SERVICE_ALL_ACCESS, win.SERVICE_WIN32_OWN_PROCESS, win.SERVICE_AUTO_START,
win.SERVICE_ERROR_NORMAL, winut.Getwd()+"\\my-service.exe", nil, nil, nil, nil, nil)
if service == 0 {
return
}
defer win.CloseServiceHandle(service)
desc := win.SERVICE_DESCRIPTION{
LpDescription: winut.NewUTF16("サービスの説明"),
}
win.LockServiceDatabase(scManager)
win.ChangeServiceConfig2(service, win.SERVICE_CONFIG_DESCRIPTION, uintptr(unsafe.Pointer(&desc)))
win.UnlockServiceDatabase(scManager)
win.StartService(service, 0, nil)
}
func unregisterService() {
scManager := win.OpenSCManager(nil, nil, win.SC_MANAGER_CONNECT)
if scManager == 0 {
return
}
service := win.OpenService(scManager, "my-service", win.SERVICE_STOP|win.SERVICE_QUERY_STATUS|win.DELETE)
if service == 0 {
return
}
defer win.CloseServiceHandle(service)
var ss win.SERVICE_STATUS
win.QueryServiceStatus(service, &ss)
if ss.DwCurrentState == win.SERVICE_RUNNING {
win.ControlService(service, win.SERVICE_CONTROL_STOP, &ss)
}
win.DeleteService(service)
}
var serviceStatus win.SERVICE_STATUS
var serviceHandle win.HANDLE
var events []win.HANDLE
func serviceHandler(control, eventType uint32, eventData, context uintptr) uintptr {
switch control {
case win.SERVICE_CONTROL_STOP:
serviceStatus.DwCurrentState = win.SERVICE_STOP_PENDING
serviceStatus.DwCheckPoint = 0
serviceStatus.DwWaitHint = 1000
win.SetServiceStatus(serviceHandle, &serviceStatus)
win.SetEvent(events[0])
case win.SERVICE_CONTROL_INTERROGATE:
win.SetServiceStatus(serviceHandle, &serviceStatus)
}
return uintptr(uint32(0))
}
func serviceMain(numServicesArgs uint32, serviceArgVectors *uint16) uintptr {
events = make([]win.HANDLE, 1)
events[0] = win.CreateEvent(nil, win.FALSE, win.FALSE, nil)
defer win.CloseHandle(events[0])
serviceHandle = win.RegisterServiceCtrlHandlerEx("my-service", syscall.NewCallback(serviceHandler), uintptr(unsafe.Pointer(nil)))
serviceStatus.DwServiceType = win.SERVICE_WIN32_OWN_PROCESS
serviceStatus.DwCurrentState = win.SERVICE_RUNNING
serviceStatus.DwControlsAccepted = win.SERVICE_ACCEPT_STOP
serviceStatus.DwWin32ExitCode = 0
serviceStatus.DwServiceSpecificExitCode = 0
serviceStatus.DwCheckPoint = 0
serviceStatus.DwWaitHint = 0
win.SetServiceStatus(serviceHandle, &serviceStatus)
win.WaitForMultipleObjects(uint32(len(events)), &events[0], win.TRUE, win.INFINITE)
serviceStatus.DwCurrentState = win.SERVICE_STOPPED
serviceStatus.DwCheckPoint = 0
serviceStatus.DwWaitHint = 0
win.SetServiceStatus(serviceHandle, &serviceStatus)
return uintptr(unsafe.Pointer(nil))
}
func main() {
if len(os.Args) >= 2 {
if os.Args[1] == "/register" || os.Args[1] == "-register" {
registerService()
} else if os.Args[1] == "/unregister" || os.Args[1] == "-unregister" {
unregisterService()
}
} else {
services := []win.SERVICE_TABLE_ENTRY{
{
LpServiceName: winut.NewUTF16("my-service"),
LpServiceProc: syscall.NewCallback(serviceMain),
},
{
LpServiceName: nil,
LpServiceProc: uintptr(unsafe.Pointer(nil)),
},
}
win.StartServiceCtrlDispatcher(&services[0])
}
}
winut.go
// package, import は省略
func NewUTF16(s string) *uint16 {
result, _ := syscall.UTF16PtrFromString(s)
return result
}
func Getwd() string {
dir, _ := os.Getwd()
return dir
}
というか、普通にあったんですねこういうの。
https://godoc.org/golang.org/x/sys/windows/svc/example