digitalmars.D - $100 bounty for help with Windows Service code
- Tyler Jensen (342/342) Aug 17 2014 $100 bounty offered!
- Vladimir Panteleev (28/30) Aug 17 2014 Hi,
- ketmar via Digitalmars-d (4/4) Aug 17 2014 On Sun, 17 Aug 2014 22:12:06 +0000
- Vladimir Panteleev (6/22) Aug 17 2014 Erm, forgot about this bit. Clearly this is a different problem
- Tyler Jensen (6/29) Aug 17 2014 GetLastError on both returns 0. I had misread the
- Tyler Jensen (6/10) Aug 17 2014 I also changed my worker thread to write to a different file. It
- ketmar via Digitalmars-d (7/7) Aug 17 2014 On Sun, 17 Aug 2014 23:56:54 +0000
- Tyler Jensen (7/17) Aug 17 2014 Now have all file i/o ops in a try/catch and ignoring errors just
- Vladimir Panteleev (10/29) Aug 17 2014 I would recommend wrapping the entire main() function in a
- Tyler Jensen (363/393) Aug 17 2014 Good idea. I'll work to make this more robust before putting it
- Etienne (1/1) Aug 18 2014 Shouldn't you be starting with rt_init() in ServiceMain?
- Vladimir Panteleev (5/6) Aug 18 2014 The runtime should be already initialized before main() is run,
- Tyler Jensen (9/15) Aug 18 2014 I did try the suggested thread_attachThis() but it did not help.
- Etienne (2/4) Aug 18 2014 Where does `mixin DECLARE_HANDLE!` come from?
- Etienne (4/8) Aug 18 2014 Nevermind, it looks like the git mirror is out of date. It would be
- Vladimir Panteleev (7/17) Aug 18 2014 Which git mirror are you talking about?
- Etienne (5/6) Aug 18 2014 Oh! I really thought it was this:
- Tyler Jensen (3/9) Aug 18 2014 Browse the source of the latter link and you'll find Vladimir's
- Vladimir Panteleev (2/6) Aug 18 2014 That's because I've just added it :)
- Tyler Jensen (4/11) Aug 21 2014 Here's the result of refactoring it.
- Kagamin (3/3) Aug 22 2014 https://github.com/duovia/WindowsServiceInD/blob/master/src/onedge/mysvc...
- Tyler Jensen (4/7) Aug 22 2014 Yes, Kagamin, you're right. I'll pull those out of shared as the
$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; } } }
Aug 17 2014
On Sunday, 17 August 2014 at 21:24:24 UTC, Tyler Jensen wrote:I've coded up a Windows Service that ALMOST works but I'm missing something. I need your help.Hi, The service works as expected on my Windows machine, but it shows the symptoms you described on my Windows XP VM. I'm going to assume that the symptoms indicate the same problem for my VM and your system. The reason for the 1053 error is that the service hangs before it can contact the service manager. It is terminated after 5 seconds. If you use Process Explorer (freeware) or Process Hacker (open-source) to peek at the service process's stack trace, you'll notice that it shows a message box. The message box is not visible to the desktop user, because it is being displayed on the hidden service window station. When the D runtime encounters an unhandled exception, it will print the exception stack trace to standard error stream, if it is valid. However, services have no standard output or error stream, so the runtime shows a MessageBox instead. (Incidentally, I implemented that behavior.) Thus, the service hangs because of an unhandled exception. The exception is: std.exception.ErrnoException std\stdio.d(367): Cannot open file `c:\temp\inc.log' in mode `a' (Permission denied) This occurs when the service process attempts to add a line to the log file. However, as the log is owned by the desktop user (because it was created by a process started as the desktop user), and the service process runs under as the "NT AUTHORITY\NETWORK SERVICE" user, the service process cannot write to the same log file as the process starting the service.
Aug 17 2014
On Sun, 17 Aug 2014 22:12:06 +0000 Vladimir Panteleev via Digitalmars-d <digitalmars-d puremagic.com> wrote: ah! you stole my money! ;-)
Aug 17 2014
On Sunday, 17 August 2014 at 21:24:24 UTC, Tyler Jensen wrote: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 ]]Erm, forgot about this bit. Clearly this is a different problem than the one I had on my XP VM. The log shows that SetStatus returns 0, indicating that SetServiceStatus is failing. I would suggest logging GetLastError's result for more information.
Aug 17 2014
On Sunday, 17 August 2014 at 22:38:38 UTC, Vladimir Panteleev wrote:On Sunday, 17 August 2014 at 21:24:24 UTC, Tyler Jensen wrote:GetLastError on both returns 0. I had misread the SetServiceStatus documentation and was assuming a 0 was success. Am looking into that. I'm also trying to figure out how to get access to the message box you mentioned.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 ]]Erm, forgot about this bit. Clearly this is a different problem than the one I had on my XP VM. The log shows that SetStatus returns 0, indicating that SetServiceStatus is failing. I would suggest logging GetLastError's result for more information.
Aug 17 2014
On Sunday, 17 August 2014 at 23:56:56 UTC, Tyler Jensen wrote:GetLastError on both returns 0. I had misread the SetServiceStatus documentation and was assuming a 0 was success. Am looking into that. I'm also trying to figure out how to get access to the message box you mentioned.I also changed my worker thread to write to a different file. It does not end the process after 5 seconds. It continues to write to the file every 5 seconds until it finds "stopping" to be true. So currently it continues to write to the worker thread file until I kill the process.
Aug 17 2014
On Sun, 17 Aug 2014 23:56:54 +0000 Tyler Jensen via Digitalmars-d <digitalmars-d 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 =3D File("...")'). also, you don't need to manually close the file, it will be automatically closed when file variable goes out of scope.
Aug 17 2014
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 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. 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?
Aug 17 2014
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:I would recommend wrapping the entire main() function in a try/catch block, and logging any caught exceptions.On Sun, 17 Aug 2014 23:56:54 +0000 Tyler Jensen via Digitalmars-d <digitalmars-d 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.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.
Aug 17 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:Good idea. I'll work to make this more robust before putting it to a blog post.On Monday, 18 August 2014 at 00:07:57 UTC, ketmar via Digitalmars-d wrote:I would recommend wrapping the entire main() function in a try/catch block, and logging any caught exceptions.On Sun, 17 Aug 2014 23:56:54 +0000 Tyler Jensen via Digitalmars-d <digitalmars-d 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.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; } } }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.
Aug 17 2014
Shouldn't you be starting with rt_init() in ServiceMain?
Aug 18 2014
On Monday, 18 August 2014 at 15:12:58 UTC, Etienne wrote:Shouldn't you be starting with rt_init() in ServiceMain?The runtime should be already initialized before main() is run, however, now that you mention it, I think ServiceMain should call thread_attachThis(). I see there is a commented-out line in the code to do this already.
Aug 18 2014
On Monday, 18 August 2014 at 15:42:26 UTC, Vladimir Panteleev wrote:On Monday, 18 August 2014 at 15:12:58 UTC, Etienne wrote:I did try the suggested thread_attachThis() but it did not help. Not saying it should not be done, but it was not the cause or the solution. Ultimately, it was the HANDLE that was the problem. Thanks, to Vladimir, this has been resolved. I will continue to experiment with and try to create my own "reference" implementation for a Windows Service since much of what I plan to write in D will be deployed as such.Shouldn't you be starting with rt_init() in ServiceMain?The runtime should be already initialized before main() is run, however, now that you mention it, I think ServiceMain should call thread_attachThis(). I see there is a commented-out line in the code to do this already.
Aug 18 2014
On 2014-08-17 9:11 PM, Vladimir Panteleev wrote:Please try changing the definition of SERVICE_STATUS_HANDLE from DWORD to size_t. I'll commit a fix to the win32 bindings repository.Where does `mixin DECLARE_HANDLE!` come from?
Aug 18 2014
On 2014-08-18 11:29 AM, Etienne wrote:On 2014-08-17 9:11 PM, Vladimir Panteleev wrote:Nevermind, it looks like the git mirror is out of date. It would be useful to have as a dub project, maybe I could make a dub.json if you promise to push your changes to it? ;)Please try changing the definition of SERVICE_STATUS_HANDLE from DWORD to size_t. I'll commit a fix to the win32 bindings repository.Where does `mixin DECLARE_HANDLE!` come from?
Aug 18 2014
On Monday, 18 August 2014 at 15:33:42 UTC, Etienne wrote:On 2014-08-18 11:29 AM, Etienne wrote:Which git mirror are you talking about? I have two auto-updating git mirrors for the win32 bindings: The entire bindings project: https://github.com/CS-svnmirror/dsource-bindings Just the win32 package (useful for git submodules): https://github.com/CS-svnmirror/dsource-bindings-win32On 2014-08-17 9:11 PM, Vladimir Panteleev wrote:Nevermind, it looks like the git mirror is out of date. It would be useful to have as a dub project, maybe I could make a dub.json if you promise to push your changes to it? ;)Please try changing the definition of SERVICE_STATUS_HANDLE from DWORD to size_t. I'll commit a fix to the win32 bindings repository.Where does `mixin DECLARE_HANDLE!` come from?
Aug 18 2014
On 2014-08-18 11:38 AM, Vladimir Panteleev wrote:Which git mirror are you talking about?Oh! I really thought it was this: https://github.com/AndrejMitrovic/WindowsAPI That's because there's a link to it on the dsource page http://www.dsource.org/projects/bindings/wiki/WindowsApi
Aug 18 2014
On Monday, 18 August 2014 at 18:24:15 UTC, Etienne wrote:On 2014-08-18 11:38 AM, Vladimir Panteleev wrote:Browse the source of the latter link and you'll find Vladimir's fix. Andrej's mirror is not updated yet.Which git mirror are you talking about?Oh! I really thought it was this: https://github.com/AndrejMitrovic/WindowsAPI That's because there's a link to it on the dsource page http://www.dsource.org/projects/bindings/wiki/WindowsApi
Aug 18 2014
On Monday, 18 August 2014 at 21:12:15 UTC, Tyler Jensen wrote:On Monday, 18 August 2014 at 18:24:15 UTC, Etienne wrote:That's because I've just added it :)http://www.dsource.org/projects/bindings/wiki/WindowsApiBrowse the source of the latter link and you'll find Vladimir's fix.
Aug 18 2014
On Monday, 18 August 2014 at 21:23:38 UTC, Vladimir Panteleev wrote:On Monday, 18 August 2014 at 21:12:15 UTC, Tyler Jensen wrote:Here's the result of refactoring it. https://github.com/duovia/WindowsServiceInDOn Monday, 18 August 2014 at 18:24:15 UTC, Etienne wrote:That's because I've just added it :)http://www.dsource.org/projects/bindings/wiki/WindowsApiBrowse the source of the latter link and you'll find Vladimir's fix.
Aug 21 2014
https://github.com/duovia/WindowsServiceInD/blob/master/src/onedge/mysvc.d#L20 these are not necessarily static: it makes sense to have instance fields of shared types too.
Aug 22 2014
On Friday, 22 August 2014 at 09:09:22 UTC, Kagamin wrote:https://github.com/duovia/WindowsServiceInD/blob/master/src/onedge/mysvc.d#L20 these are not necessarily static: it makes sense to have instance fields of shared types too.Yes, Kagamin, you're right. I'll pull those out of shared as the OnStart, OnStop, and other "On" methods get called by the SCM on the same thread. I'll make that change. Thanks.
Aug 22 2014