dotj78335
2019-08-15 17:02
浏览 61
已采纳

如何从Windows服务运行另一个可执行文件

Besides a few tutorials on Go I have no actual experience in it. I'm trying to take a project written in Go and converting it into a windows service.

I honestly haven't tried anything besides trying to find things to read over. I have found a few threads and choosen the best library I felt covered all of our needs

https://github.com/golang/sys

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build windows

package main

import (
    "fmt"
    "strings"
    "time"

    "golang.org/x/sys/windows/svc"
    "golang.org/x/sys/windows/svc/debug"
    "golang.org/x/sys/windows/svc/eventlog"
)

var elog debug.Log

type myservice struct{}

func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
    const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
    changes <- svc.Status{State: svc.StartPending}
    fasttick := time.Tick(500 * time.Millisecond)
    slowtick := time.Tick(2 * time.Second)
    tick := fasttick
    changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
    for {
        select {
        case <-tick:
            beep()
            elog.Info(1, "beep")
        case c := <-r:
            switch c.Cmd {
            case svc.Interrogate:
                changes <- c.CurrentStatus
                // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
                time.Sleep(100 * time.Millisecond)
                changes <- c.CurrentStatus
            case svc.Stop, svc.Shutdown:
                // golang.org/x/sys/windows/svc.TestExample is verifying this output.
                testOutput := strings.Join(args, "-")
                testOutput += fmt.Sprintf("-%d", c.Context)
                elog.Info(1, testOutput)
                break loop
            case svc.Pause:
                changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
                tick = slowtick
            case svc.Continue:
                changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
                tick = fasttick
            default:
                elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
            }
        }
    }
    changes <- svc.Status{State: svc.StopPending}
    return
}

func runService(name string, isDebug bool) {
    var err error
    if isDebug {
        elog = debug.New(name)
    } else {
        elog, err = eventlog.Open(name)
        if err != nil {
            return
        }
    }
    defer elog.Close()

    elog.Info(1, fmt.Sprintf("starting %s service", name))
    run := svc.Run
    if isDebug {
        run = debug.Run
    }
    err = run(name, &myservice{})
    if err != nil {
        elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
        return
    }
    elog.Info(1, fmt.Sprintf("%s service stopped", name))
}

So I spent some time going over this code. Tested it out to see what it does. It performs as it should.

The question I have is we currently have a Go program that takes in arguments and for our service we pass in server. Which spins up our stuff on a localhost webpage.

I believe the code above may have something to do with that but I'm lost at how I would actually get it spin off our exe with the correct arguements. Is this the right spot to call main?

Im sorry if this is vague. I dont know exactly how to make this interact with our already exisiting exe.

I can get that modified if I know what needs to be changed. I appreacite any help.

  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • douxunnian0423 2019-08-15 18:02
    已采纳

    OK, that's much clearer now. Well, ideally you should start with some tutorial on what constitutes a Windows service—I bet tihis might have solved the problem for you. But let's try anyway.

    Some theory

    A Windows service sort of has two facets: it performs some useful task and it communicates with the SCM facility. When you manipulate a service using the sc command or through the Control Panel, you have that piece of software to talk with SCM on your behalf, and SCM talks with that service.

    The exact protocol the SCM and a service use is low-level and complicated and the point of the Go package you're using is to hide that complexity from you and offer a reasonably Go-centric interface to that stuff.

    As you might gather from your own example, the Execute method of the type you've created is—for the most part—concerned with communicating with SCM: it runs an endless for loop which on each iteration sleeps on reading from the r channel, and that channel delivers SCM commands to your service.

    So you basically have what could be called "an SCM command processing loop".

    Now recall those two facets above. You already have one of them: your service interacts with SCM, so you need another one—the code which actually performs useful tasks.

    In fact, it's already partially there: the example code you've grabbed creates a time ticker which provides a channel on which it delivers a value when another tick passes. The for loop in the Execute method reads from that channel as well, "doing work" each time another tick is signalled.

    OK, this is fine for a toy example but lame for a real work.

    Approaching the solution

    So let's pause for a moment and think about our requirements.

    1. We need some code running and doing our actual task(s).
    2. We need the existing command processing loop to continue working.
    3. We need these two pieces of code to work concurrently.

    In this toy example the 3rd point is there "for free" because a time ticker carries out the task of waiting for the next tick automatically and fully concurrently with the rest of the code.

    Your real code most probably won't have that luxury, so what do you do?

    In Go, when you need to do something concurrently with something else, an obvious answer is "use a goroutine".

    So the first step is to grab your existing code, turn it into a callable function and then call it in a separate goroutine right before entering the for loop. This way, you'll have both pieces run concurrently.

    The hard parts

    OK, that wasn't hard.

    The hard parts are:

    • How to configure the code which performs the tasks.
    • How to make the SCM command processing loop and the code carrying out tasks communicate.

    Configuration

    This one really depends on the policies at your $dayjob or of your $current_project, but there are few hints:

    • A Windows service may receive command-line arguments—either for a single run or permanently (passed to the service on each of its runs).

      The downside is that it's not convenient to work with them from the UI/UX standpoint.

    • Typically Windows services used to read the registry.

    • These days (after the advent of .NET and its pervasive xml-ity) the services tend to read configuration files.

    • The OS environment most of the time is a bad fit for the task.

    • You may combine several of these venues.

    I think I'd start with a configuration file but then again, you should pick the path of the least resistance, I think.

    One of the things to keep in mind is that the reading and processing of the configuration should better be done before the service signals the SCM it started OK: if the configuration is invalid or cannot be loaded, the service should extensively log that and signal it failed, and not run the actual task processing code.

    Communication between the command processing loop and the tasks carrying code

    This is IMO the hardest part.

    It's possible to write a whole book here but let's keep it simple for now.

    To make it as simple as possible I'd do the following:

    • Consider pausing, stopping and shutting down mostly the same: all these signals must tell your task processing code to quit, and then wait for it to actually do that.
    • Consider the "continue" signal the same as starting the task processing function: run it again—in a new goroutine.
    • Have a one-directional communication: from the control loop to the tasks processing code, but not the other way—this will greatly simplify service state management.

    This way, you may create a single channel which the task processing code listens on—or checks periodically, and when a value comes from that channel, the code stops running, closes the channel and exits.

    The control loop, when the SCM tells it to pause or stop or shut down, sends anything on that channel then waits for it to close. When that happens, it knows the tasks processing code is finished.

    In Go, a paradigm for a channel which is only used for signaling, is to have a channel of type struct{} (an empty struct).

    The question of how to monitor this control channel in the tasks running code is an open one and heavily depends on the nature of the tasks it performs.

    Any further help here would be reciting what's written in the Go books on concurrency so you should have that covered first.


    There's also an interesting question of how to have the communication between the control loop and the tasks processing loop resilient to the possible processing stalls in the latter, but then again, IMO it's too early to touch upon that.

    已采纳该答案
    打赏 评论

相关推荐 更多相似问题