$100 bounty for help with Windows Service code

Tyler Jensen via Digitalmars-d digitalmars-d at puremagic.com
Sun Aug 17 18:25:18 PDT 2014


On Monday, 18 August 2014 at 01:11:37 UTC, Vladimir Panteleev 
wrote:
> On Monday, 18 August 2014 at 00:37:15 UTC, Tyler Jensen wrote:
>> On Monday, 18 August 2014 at 00:07:57 UTC, ketmar via 
>> Digitalmars-d wrote:
>>> On Sun, 17 Aug 2014 23:56:54 +0000
>>> Tyler Jensen via Digitalmars-d <digitalmars-d at puremagic.com> 
>>> wrote:
>>>
>>> try to catch and process exceptions where they may arose. any 
>>> file
>>> operation can throw exception (yes, even innocent-looking 
>>> writeln(),
>>> let alone 'auto fl = File("...")').
>>>
>>> also, you don't need to manually close the file, it will be
>>> automatically closed when file variable goes out of scope.
>>
>> Now have all file i/o ops in a try/catch and ignoring errors 
>> just to see if that has anything to do with it.
>
> I would recommend wrapping the entire main() function in a 
> try/catch block, and logging any caught exceptions.

Good idea. I'll work to make this more robust before putting it 
to a blog post.

>
>> The answer is no. I'm still getting result of 0 and a 
>> GetLastError return of ERROR_INVALID_HANDLE. Now I just need 
>> to figure out why the serviceStatusHandle is invalid. Ideas?
>
> I think I found the problem.
>
> In winsvc.d, SERVICE_STATUS_HANDLE is incorrectly declared as a 
> DWORD (4-byte integer), when it should be declared as a HANDLE 
> (8 bytes on 64-bit platforms).
>
> Please try changing the definition of SERVICE_STATUS_HANDLE 
> from DWORD to size_t. I'll commit a fix to the win32 bindings 
> repository.

FANTASTIC! That little gem fixed it all up. With this change to 
winsvc.d:

//original: alias DWORD SERVICE_STATUS_HANDLE;
alias size_t SERVICE_STATUS_HANDLE;

And with the code below, you'll have an x64 Windows Service. The 
code is not sufficient robust for production but it serves as a 
working example with the fix to winsvc.d.

Vladimir, you win the $100. Thanks for doggedly helping me solve 
this. It's very much appreciated and comes as a great relief. 
Shoot me a private email with an address or PayPal account I can 
use to send you the bounty.

Here's the final service code:

import core.sync.mutex : Mutex;
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)
{
     try
     {
         File f = File(r"c:\temp\inc.log", "a");
         auto tid = GetCurrentThreadId();
         if (tid)
             f.writeln(args, ", tid: ", tid);
         else
             f.writeln(args);
     }
     catch
     {

     }
}

void logItWt(T...)(T args)
{
     try
     {
         File f = File(r"c:\temp\incwt.log", "a");
         auto tid = GetCurrentThreadId();
         if (tid)
             f.writeln(args, ", tid: ", tid);
         else
             f.writeln(args);
     }
     catch
     {
     }
}

//void ServiceControlHandler(DWORD controlCode)

extern (Windows)
DWORD ServiceControlHandler(DWORD controlCode, DWORD eventType,
                             void* eventData, void* context)
{
     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;
     }
     return NO_ERROR;
}

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

     // initialise service status
     //with (serviceStatus)
     //{

     //DWORD dwServiceType;
     //DWORD dwCurrentState;
     //DWORD dwControlsAccepted;
     //DWORD dwWin32ExitCode;
     //DWORD dwServiceSpecificExitCode;
     //DWORD dwCheckPoint;
     //DWORD dwWaitHint;

         serviceStatus.dwServiceType		= SERVICE_WIN32_OWN_PROCESS;
         serviceStatus.dwCurrentState		= SERVICE_STOPPED;
         serviceStatus.dwControlsAccepted = 0; //|= 
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
         serviceStatus.dwWin32ExitCode		= NO_ERROR;
         serviceStatus.dwServiceSpecificExitCode = 0;
         serviceStatus.dwCheckPoint		      = 0;
         serviceStatus.dwWaitHint		          = 3000;
     //}

     Sleep(1000); //test delay before getting handler

     serviceStatusHandle = 
RegisterServiceCtrlHandlerEx(serviceName,
                                                        
&ServiceControlHandler, null);

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

     // service is starting
     serviceStatus.dwControlsAccepted = 0; //accept no controls 
while pending
     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 logItWt("worker pid: ", getpid());
                          //serve(CONTROL_PORT, logFile);
                          while (!stopping)
                          {
                              Sleep(5000);
                              logItWt("worker thread");
                          }
                          SetEvent(stopServiceEvent);
                      });
     web.isDaemon = true;
     web.start();

     // running
     serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
     auto runningStatus = SetStatus(SERVICE_RUNNING);
     debug logIt("runningStatus ", runningStatus);
}

void StopService()
{
     debug logIt("StopService called");
     serviceStatus.dwControlsAccepted = 0;
     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)
     //{
         serviceStatus.dwCurrentState = state;
         serviceStatus.dwWin32ExitCode = exitCode;
         serviceStatus.dwWaitHint = waitHint;
     //}
     auto result = SetServiceStatus(serviceStatusHandle, 
&serviceStatus);
     if (result == 0)
     {
         auto errCode = GetLastError();
         logIt("SetServiceStatus error ", errCode);
     }
     return result;
}


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

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;
         }
     }
}


More information about the Digitalmars-d mailing list