$100 bounty offered!

I've coded up a Windows Service that ALMOST works but I'm missing something. I need your help.

I obtained some code from another forum user who had obtained it from another forum. I've modified and simplified to the best of my limited ability. Install and uninstall works. The service starts and produces the following error:

[[ Error 1053: The service did not respond to the start or control request in a timely fashion. ]]

Despite the error, the process runs and the ServiceMain is called and the worker thread executes as you can see from the following logging output:

[[
main ["C:\\Code\\DPlay\\edge\\edge\\Debug DMD x64\\edge.exe"], tid: 9980
initialize, tid: 9980
RunService serviceTable.ptr 81359A3F00, tid: 9980
ServiceMain pid: 11092813551BE58, tid: 5852
RegisterServiceCtrlHandler, serviceStatusHandle 894776416, tid: 5852
pendStatus 0, tid: 5852
runningStatus 0, tid: 5852
worker pid: 11092, tid: 9964
worker thread, tid: 9964
]]

I've been struggling with this for a while now and reading everything I can find but to no avail. I'm using the latest DMD compiler with the following command line:

"$(VisualDInstallDir)pipedmd.exe" dmd -m64 -g -debug -X -Xf"$(IntDir)\$(TargetName).json" -IC:\D\dsource -deps="$(OutDir)\$(ProjectName).dep" -of"$(OutDir)\$(ProjectName).exe" -map "$(INTDIR)\$(SAFEPROJECTNAME).map"

I'm using Visual Studio 2013 on Windows 8.1 x64. The dsource win32 lib comes from dsource.org.

The first person who can help me solve this wins a $100 bounty and an honorable mention in the blog post I'll write up about it, along with the working code.

Here's the code in its entirety.

import core.thread;
import std.conv : to;
import std.process : system;
import std.stdio;
import std.string;
import win32.w32api;
import win32.winbase;
import win32.winerror;
import win32.winnt;
import win32.windef;
import win32.winsvc;
pragma(lib, "advapi32.lib");

enum SERVICE_NAME = "MyTestService";
enum DISPLAY_NAME = "My Test Service";
enum SERVICE_START_NAME = "NT AUTHORITY\\NetworkService";
enum CONTROL_PORT = 8080;

enum _MAX_PATH = 4096;

__gshared
{
    char* serviceName;
    char* displayName;
    char* serviceStartName;
    SERVICE_TABLE_ENTRY[] serviceTable;
    SERVICE_STATUS serviceStatus;
    SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
    HANDLE stopServiceEvent = null;
    Thread web;
    DWORD checkPoint = 1;
    bool stopping = false;
}

void initialize()
{
    serviceName = cast(char*) toStringz(SERVICE_NAME);
    displayName = cast(char*) toStringz(DISPLAY_NAME);
    serviceStartName = cast(char*) toStringz(SERVICE_START_NAME);
    debug logIt("initialize");
}

void logIt(T...)(T args)
{
    File f = File(r"c:\temp\inc.log", "a");
    auto tid = GetCurrentThreadId();
    if (tid)
        f.writeln(args, ", tid: ", tid);
    else
        f.writeln(args);
    f.close();
}

extern (Windows)
void ServiceControlHandler(DWORD controlCode)
{
debug logIt("ServiceControlHandler, controlCode ", controlCode);

    switch (controlCode)
    {
        case SERVICE_CONTROL_SHUTDOWN:
        case SERVICE_CONTROL_STOP:
            StopService();
            break;

        case SERVICE_CONTROL_SESSIONCHANGE:
        case SERVICE_CONTROL_PAUSE: // 2
        case SERVICE_CONTROL_CONTINUE: // 3
        case SERVICE_CONTROL_INTERROGATE: // 4
        default:
            SetStatus(serviceStatus.dwCurrentState);
            break;
    }
}

extern(Windows)
void ServiceMain(DWORD argc, TCHAR** argv)
{
    //auto mythread = thread_attachThis();
    debug logIt("ServiceMain pid: ", getpid(), argv);

    // initialise service status
    with (serviceStatus)
    {
        dwServiceType           = SERVICE_WIN32_OWN_PROCESS;
        dwCurrentState          = SERVICE_STOPPED;
dwControlsAccepted |= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
        dwWin32ExitCode         = NO_ERROR;
        dwServiceSpecificExitCode = 0;
        dwCheckPoint                  = 0;
        dwWaitHint                        = 0;
    }

    serviceStatusHandle = RegisterServiceCtrlHandler(serviceName,
&ServiceControlHandler);

debug logIt("RegisterServiceCtrlHandler, serviceStatusHandle ", serviceStatusHandle);
    if (!serviceStatusHandle)
    {
        return;
    }

    // service is starting
    auto pendStatus = SetStatus(SERVICE_START_PENDING);
    debug logIt("pendStatus ", pendStatus);

    // do initialisation here
    stopServiceEvent = CreateEvent(null, FALSE, FALSE, null);
    if (!stopServiceEvent)
    {
        debug logIt("!stopServiceEvent = CreateEvent");
    }

    //worker thread
    web = new Thread(
                     {
                         Sleep(3000);
                         debug logIt("worker pid: ", getpid());
                         //serve(CONTROL_PORT, logFile);
                         while (!stopping)
                         {
                             Sleep(5000);
                             logIt("worker thread");
                         }
                         SetEvent(stopServiceEvent);
                     });
    web.isDaemon = true;
    web.start();

    // running
    auto runningStatus = SetStatus(SERVICE_RUNNING);
    debug logIt("runningStatus ", runningStatus);
}

void StopService()
{
    debug logIt("StopService called");
    SetStatus(SERVICE_STOP_PENDING);
    stopping = true; //tell worker thread to stop

    //wait for signal
if (WaitForSingleObject(stopServiceEvent, INFINITE) != WAIT_OBJECT_0)
    {
        auto err = GetLastError();
        throw new Exception("Error: %s", to!string(err));
    }

// service was stopped signaled and SERVICE_STOP_PENDING set already - so clean up
    CloseHandle(stopServiceEvent);
    stopServiceEvent = null;

    // service is now stopped
    auto stoppedStatus = SetStatus(SERVICE_STOPPED);
    debug logIt("stoppedStatus ", stoppedStatus);
}


// Set the service status and report the status to the SCM.
DWORD SetStatus(DWORD state,
                DWORD exitCode = NO_ERROR,
                DWORD waitHint = 0)
{
serviceStatus.dwCheckPoint = ((state == SERVICE_RUNNING) || (state == SERVICE_STOPPED))
        ? 0
        : checkPoint++;
    with (serviceStatus)
    {
        dwCurrentState = state;
        dwWin32ExitCode = exitCode;
        dwWaitHint = waitHint;
    }
    return SetServiceStatus(serviceStatusHandle, &serviceStatus);
}


// ---------------------------------------------------------------------------

void RunService()
{
    serviceTable =
    [
        SERVICE_TABLE_ENTRY(serviceName, &ServiceMain),
        SERVICE_TABLE_ENTRY(null, null)
    ];

    debug logIt("RunService serviceTable.ptr ", serviceTable.ptr);
    StartServiceCtrlDispatcher(serviceTable.ptr);
}

void InstallService()
{
    SC_HANDLE serviceControlManager = OpenSCManager(null, null,
SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
    if (serviceControlManager)
    {
        TCHAR path[_MAX_PATH + 1];
        if (GetModuleFileName(null, path.ptr, path.sizeof) > 0)
        {
            SC_HANDLE service = CreateService(
serviceControlManager, cast (const) serviceName, cast (const) displayName, SERVICE_QUERY_STATUS, SERVICE_WIN32_OWN_PROCESS,
                                              SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
                                              path.ptr,
                                              null, null, null,
cast (const) serviceStartName,
                                              null);
            if (service)
                CloseServiceHandle(service);
        }

        CloseServiceHandle(serviceControlManager);
    }
}

void UninstallService()
{
    SC_HANDLE serviceControlManager = OpenSCManager(null, null,
SC_MANAGER_CONNECT);

    if (serviceControlManager)
    {
        SC_HANDLE service = OpenService(serviceControlManager,
                                        serviceName,
SERVICE_QUERY_STATUS | DELETE);
        if (service)
        {
            SERVICE_STATUS serviceStatus;
            if (QueryServiceStatus(service, &serviceStatus))
            {
if (serviceStatus.dwCurrentState == SERVICE_STOPPED)
                    DeleteService(service);
            }

            CloseServiceHandle(service);
        }

        CloseServiceHandle(serviceControlManager);
    }
}

void StartStop(bool toStart)
{
    debug logIt("StartStop");
    SC_HANDLE serviceControlManager = OpenSCManager(null, null,
SC_MANAGER_CONNECT);

    if (serviceControlManager)
    {
        SC_HANDLE service = OpenService(
                                        serviceControlManager,
                                        serviceName,
                                        SERVICE_QUERY_STATUS
                                        | SERVICE_START
                                        | SERVICE_STOP);

        if (service)
        {
            SERVICE_STATUS ss;
            uint result;

            if (toStart)
                result = StartService(service, 0, null);
            else
result = ControlService(service, SERVICE_CONTROL_STOP, &ss);

            if (result == 0) {
                uint err = GetLastError();
                if (err == 1062)
                    writeln("Already stopped!");
                else if (err == 1056)
                    writeln("Already started!");
                else
                    writeln("Error: ", err);
            }

            CloseServiceHandle(service);
        }

        CloseServiceHandle(serviceControlManager);
    }
}

void main(string[] args)
{
    debug logIt("main ", args);
    initialize();
    if (args.length < 2)
    {
        writeln("running...");
        RunService();
    }
    else
    {
        switch (args[1])
        {
            case "install":
                writeln("installing...");
                InstallService();
                break;
            case "uninstall":
                writeln("uninstalling...");
                UninstallService();
                break;
            case "start":
                writeln("starting...");
                StartStop(true);
                break;
            case "stop":
                writeln("stopping...");
                StartStop(false);
                break;
            default:
                writefln("%s: unknown command: %s",
                         to!string(serviceName), args[1]);
                break;
        }
    }
}

Reply via email to