LoginSignup
1
2

More than 3 years have passed since last update.

Go で Windows Service を作成する

Last updated at Posted at 2020-04-26

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2