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