如何将用Golang编写的应用程序作为Windows服务运行



我不能将使用Win32 api开发的屏幕截图应用程序用作Windows后台服务。我把它作为Windows后台服务安装并运行,到目前为止我没有问题。我的问题是:服务没有给我打印件。不需要截屏。我试着制作另一个简单的应用程序。我尝试使用OutputDebugStringW函数发送消息,但问题没有得到解决。

  • 无法使用Win32 api开发Windows后台应用程序
  • 为什么我有这个问题
  • 如何使用win32api将屏幕截图应用程序作为windows后台服务运行

我的Windows后台服务没有产生输出

package main

import (
"fmt"
"log"
"time"

"github.com/checkgo/win"
"github.com/kardianos/service"
)

var logger service.Logger

type program struct {
exit chan struct{}
}

func (p *program) Start(s service.Service) error {
if service.Interactive() {
logger.Info("Running in terminal.")
} else {
logger.Info("Running under service manager.")
}
p.exit = make(chan struct{})

// Start should not block. Do the actual work async.
go p.run()
return nil
}
func (p *program) run() {
logger.Infof("I'm running %v.", service.Platform())
ticker := time.NewTicker(2 * time.Second)
for {
select {
case tm := <-ticker.C:
win.OutputDebugString(fmt.Sprintf("%s : %v", "This is test message", tm))
case <-p.exit:
ticker.Stop()
}
} // link to whaterver image from the web

}
func (p *program) Stop(s service.Service) error {
// Stop should not block. Return with a few seconds.
return nil
}

func main() {
svcConfig := &service.Config{
Name:        "GoServiceExampleSimple",
DisplayName: "Go Service Example",
Description: "This is an example Go service.",
}

prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
}
logger, err = s.Logger(nil)
if err != nil {
log.Fatal(err)
}
err = s.Run()
if err != nil {
logger.Error(err)
}
}

屏幕截图:DebugView和Windows服务屏幕捕获

使用CreateProcessAsUser函数求解。

我用来解决问题的GOO源代码。

Windows后台服务应用程序的源代码:

package main
import (
"log"
"reflect"
"sync"
"syscall"
"github.com/checkgo/win"
"github.com/kardianos/service"
"golang.org/x/sys/windows"
)
var logger service.Logger
type program struct {
exit chan struct{}
}
func (p *program) Start(s service.Service) error {
if service.Interactive() {
logger.Info("Running in terminal.")
} else {
logger.Info("Running under service manager.")
}
p.exit = make(chan struct{})
// Start should not block. Do the actual work async.
go p.run()
return nil
}
func (p *program) run() {
var wgT sync.WaitGroup
wgT.Add(1)
test(&wgT)
wgT.Wait()
}
func test(wg *sync.WaitGroup) {
var saAttr win.SECURITY_ATTRIBUTES
saAttr.NLength = uint32(reflect.TypeOf(syscall.SecurityAttributes{}).Size())
saAttr.BInheritHandle = win.TRUE
saAttr.LpSecurityDescriptor = uintptr(0)
var si syscall.StartupInfo
si.Cb = uint32(reflect.TypeOf(syscall.SecurityAttributes{}).Size())
si.Desktop = windows.StringToUTF16Ptr("Winsta0\default")
si.Flags = windows.STARTF_USESTDHANDLES
var hToken windows.Token
id := win.WTSGetActiveConsoleSessionId()
err := windows.WTSQueryUserToken(uint32(id), &hToken)
if err != nil {
logger.Info(err)
}
path := windows.StringToUTF16Ptr("C:\Users\cingo\go\src\srv\agent\test_agent.exe")
var pi syscall.ProcessInformation
syscall.CreateProcessAsUser(syscall.Token(hToken),
path,
nil,
nil,
nil,
true,
windows.CREATE_NO_WINDOW,
nil,
nil,
&si,
&pi)
}
func (p *program) Stop(s service.Service) error {
// Stop should not block. Return with a few seconds.
return nil
}
func main() {
svcConfig := &service.Config{
Name:        "GoServiceExampleSimple",
DisplayName: "Go Service Example",
Description: "This is an example Go service.",
}
prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
}
logger, err = s.Logger(nil)
if err != nil {
log.Fatal(err)
}
err = s.Run()
if err != nil {
logger.Error(err)
}
}

运行应用程序

package main
import (
"fmt"
"github.com/checkgo/win"
"time"
)
func main() {
for {
time.Sleep(time.Second * 1)
win.OutputDebugString(fmt.Sprintf("%s", "This is test message"))
}
}

如果服务在LocalSystem的安全上下文中运行帐户,但不包括SERVICE_INTERACTIVE_PROCESS属性,它使用以下窗口站和桌面:服务-0x0-3e7$\默认值。这个窗口站不是交互式的,所以该服务无法显示用户界面。此外由服务创建的无法显示用户界面。

参考:窗口站和桌面创建

交互式窗口站是唯一可以显示用户界面或接收用户输入。它被分配给交互用户的登录会话并且包含键盘,鼠标和显示设备。它总是被命名为";WinSta0";。所有其他窗口工作站是非交互式的,这意味着它们不能显示用户界面或接收用户输入。

参考:窗口站

据说您要捕获的屏幕位于交互式窗口站的默认桌面(Winsta0\default(。您可以在Winsta0default中创建一个子进程,该子进程用于屏幕截图。使用CreateProcessAsUser调用服务中的子进程。

请参阅以下C++代码。虽然这不是一种Go语言,但我认为熟悉winapi的调用就足够了。

DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
{
OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry"));
WCHAR station[] = L"Winsta0\default";
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;

STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = station;
si.dwFlags = STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
HANDLE hToken;
bool err = WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken);
WCHAR path[] = L"D:\child.exe";  //child.exe is used to take screenshots
if (CreateProcessAsUser(hToken, path, NULL, 0, 0, true, CREATE_NO_WINDOW, 0, 0, &si, &pi))
{
...

最新更新