win32-service 0.8.6 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,364 +1,364 @@
1
- require File.join(File.dirname(__FILE__), 'windows', 'helper')
2
- require File.join(File.dirname(__FILE__), 'windows', 'constants')
3
- require File.join(File.dirname(__FILE__), 'windows', 'structs')
4
- require File.join(File.dirname(__FILE__), 'windows', 'functions')
5
-
6
- require 'ffi'
7
-
8
- # The Win32 module serves as a namespace only.
9
- module Win32
10
-
11
- # The Daemon class
12
- class Daemon
13
- include Windows::ServiceConstants
14
- include Windows::Structs
15
- include Windows::Functions
16
-
17
- extend Windows::Structs
18
- extend Windows::Functions
19
-
20
- # The version of this library
21
- VERSION = '0.8.6'
22
-
23
- private
24
-
25
- # Service is not running
26
- STOPPED = SERVICE_STOPPED
27
-
28
- # Service has received a start signal but is not yet running
29
- START_PENDING = SERVICE_START_PENDING
30
-
31
- # Service has received a stop signal but is not yet stopped
32
- STOP_PENDING = SERVICE_STOP_PENDING
33
-
34
- # Service is running
35
- RUNNING = SERVICE_RUNNING
36
-
37
- # Service has received a signal to resume but is not yet running
38
- CONTINUE_PENDING = SERVICE_CONTINUE_PENDING
39
-
40
- # Service has received a signal to pause but is not yet paused
41
- PAUSE_PENDING = SERVICE_PAUSE_PENDING
42
-
43
- # Service is paused
44
- PAUSED = SERVICE_PAUSED
45
-
46
- # Service controls
47
-
48
- # Notifies service that it should stop
49
- CONTROL_STOP = SERVICE_CONTROL_STOP
50
-
51
- # Notifies service that it should pause
52
- CONTROL_PAUSE = SERVICE_CONTROL_PAUSE
53
-
54
- # Notifies service that it should resume
55
- CONTROL_CONTINUE = SERVICE_CONTROL_CONTINUE
56
-
57
- # Notifies service that it should return its current status information
58
- CONTROL_INTERROGATE = SERVICE_CONTROL_INTERROGATE
59
-
60
- # Notifies a service that its parameters have changed
61
- CONTROL_PARAMCHANGE = SERVICE_CONTROL_PARAMCHANGE
62
-
63
- # Notifies a service that there is a new component for binding
64
- CONTROL_NETBINDADD = SERVICE_CONTROL_NETBINDADD
65
-
66
- # Notifies a service that a component for binding has been removed
67
- CONTROL_NETBINDREMOVE = SERVICE_CONTROL_NETBINDREMOVE
68
-
69
- # Notifies a service that a component for binding has been enabled
70
- CONTROL_NETBINDENABLE = SERVICE_CONTROL_NETBINDENABLE
71
-
72
- # Notifies a service that a component for binding has been disabled
73
- CONTROL_NETBINDDISABLE = SERVICE_CONTROL_NETBINDDISABLE
74
-
75
- IDLE = 0
76
-
77
- # Wraps SetServiceStatus.
78
- SetTheServiceStatus = Proc.new do |dwCurrentState, dwWin32ExitCode,dwCheckPoint, dwWaitHint|
79
- ss = SERVICE_STATUS.new # Current status of the service.
80
-
81
- # Disable control requests until the service is started.
82
- if dwCurrentState == SERVICE_START_PENDING
83
- ss[:dwControlsAccepted] = 0
84
- else
85
- ss[:dwControlsAccepted] =
86
- SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|
87
- SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_SHUTDOWN
88
- end
89
-
90
- # Initialize ss structure.
91
- ss[:dwServiceType] = SERVICE_WIN32_OWN_PROCESS
92
- ss[:dwServiceSpecificExitCode] = 0
93
- ss[:dwCurrentState] = dwCurrentState
94
- ss[:dwWin32ExitCode] = dwWin32ExitCode
95
- ss[:dwCheckPoint] = dwCheckPoint
96
- ss[:dwWaitHint] = dwWaitHint
97
-
98
- @@dwServiceState = dwCurrentState
99
-
100
- # Send status of the service to the Service Controller.
101
- if !SetServiceStatus(@@ssh, ss)
102
- SetEvent(@@hStopEvent)
103
- end
104
- end
105
-
106
- ERROR_CALL_NOT_IMPLEMENTED = 0x78
107
-
108
- # Handles control signals from the service control manager.
109
- Service_Ctrl_ex = Proc.new do |dwCtrlCode,dwEventType,lpEventData,lpContext|
110
- @@waiting_control_code = dwCtrlCode;
111
- return_value = NO_ERROR
112
-
113
- begin
114
- dwState = SERVICE_RUNNING
115
-
116
- case dwCtrlCode
117
- when SERVICE_CONTROL_STOP
118
- dwState = SERVICE_STOP_PENDING
119
- when SERVICE_CONTROL_SHUTDOWN
120
- dwState = SERVICE_STOP_PENDING
121
- when SERVICE_CONTROL_PAUSE
122
- dwState = SERVICE_PAUSED
123
- when SERVICE_CONTROL_CONTINUE
124
- dwState = SERVICE_RUNNING
125
- #else
126
- # TODO: Handle other control codes? Retain the current state?
127
- end
128
-
129
- # Set the status of the service except on interrogation.
130
- unless dwCtrlCode == SERVICE_CONTROL_INTERROGATE
131
- SetTheServiceStatus.call(dwState, NO_ERROR, 0, 0)
132
- end
133
-
134
- # Tell service_main thread to stop.
135
- if dwCtrlCode == SERVICE_CONTROL_STOP || dwCtrlCode == SERVICE_CONTROL_SHUTDOWN
136
- if SetEvent(@@hStopEvent) == 0
137
- SetTheServiceStatus.call(SERVICE_STOPPED, FFI.errno, 0, 0)
138
- end
139
- end
140
- rescue
141
- return_value = ERROR_CALL_NOT_IMPLEMENTED
142
- end
143
-
144
- return_value
145
- end
146
-
147
- # Called by the service control manager after the call to StartServiceCtrlDispatcher.
148
- Service_Main = FFI::Function.new(:void, [:ulong, :pointer], :blocking => false) do |dwArgc,lpszArgv|
149
- begin
150
- # Obtain the name of the service.
151
- if lpszArgv.address!=0
152
- argv = lpszArgv.get_array_of_string(0,dwArgc)
153
- lpszServiceName = argv[0]
154
- else
155
- lpszServiceName = ''
156
- end
157
-
158
- # Args passed to Service.start
159
- if(dwArgc > 1)
160
- @@Argv = argv[1..-1]
161
- else
162
- @@Argv = nil
163
- end
164
-
165
- # Register the service ctrl handler.
166
- @@ssh = RegisterServiceCtrlHandlerEx(
167
- lpszServiceName,
168
- Service_Ctrl_ex,
169
- nil
170
- )
171
-
172
- # No service to stop, no service handle to notify, nothing to do but exit.
173
- return if @@ssh == 0
174
-
175
- # The service has started.
176
- SetTheServiceStatus.call(SERVICE_RUNNING, NO_ERROR, 0, 0)
177
-
178
- SetEvent(@@hStartEvent)
179
-
180
- # Main loop for the service.
181
- while(WaitForSingleObject(@@hStopEvent, 1000) != WAIT_OBJECT_0) do
182
- end
183
-
184
- # Main loop for the service.
185
- while(WaitForSingleObject(@@hStopCompletedEvent, 1000) != WAIT_OBJECT_0) do
186
- end
187
- ensure
188
- # Stop the service.
189
- SetTheServiceStatus.call(SERVICE_STOPPED, NO_ERROR, 0, 0)
190
- end
191
- end
192
-
193
- ThreadProc = FFI::Function.new(:ulong,[:pointer]) do |lpParameter|
194
- ste = FFI::MemoryPointer.new(SERVICE_TABLE_ENTRY, 2)
195
-
196
- s = SERVICE_TABLE_ENTRY.new(ste[0])
197
- s[:lpServiceName] = FFI::MemoryPointer.from_string('')
198
- s[:lpServiceProc] = lpParameter
199
-
200
- s = SERVICE_TABLE_ENTRY.new(ste[1])
201
- s[:lpServiceName] = nil
202
- s[:lpServiceProc] = nil
203
-
204
- # No service to step, no service handle, no ruby exceptions, just terminate the thread..
205
- if !StartServiceCtrlDispatcher(ste)
206
- return 1
207
- end
208
-
209
- return 0
210
- end
211
-
212
- public
213
-
214
- # This is a shortcut for Daemon.new + Daemon#mainloop.
215
- #
216
- def self.mainloop
217
- self.new.mainloop
218
- end
219
-
220
- # This is the method that actually puts your code into a loop and allows it
221
- # to run as a service. The code that is actually run while in the mainloop
222
- # is what you defined in your own Daemon#service_main method.
223
- #
224
- def mainloop
225
- @@waiting_control_code = IDLE_CONTROL_CODE
226
- @@dwServiceState = 0
227
-
228
- # Redirect STDIN, STDOUT and STDERR to the NUL device if they're still
229
- # associated with a tty. This helps newbs avoid Errno::EBADF errors.
230
- STDIN.reopen('NUL') if STDIN.isatty
231
- STDOUT.reopen('NUL') if STDOUT.isatty
232
- STDERR.reopen('NUL') if STDERR.isatty
233
-
234
- # Calling init here so that init failures never even tries to start the
235
- # service. Of course that means that init methods must be very quick
236
- # because the SCM will be receiving no START_PENDING messages while
237
- # init's running.
238
- #
239
- # TODO: Fix?
240
- service_init() if respond_to?('service_init')
241
-
242
- # Create the event to signal the service to start.
243
- @@hStartEvent = CreateEvent(nil, true, false, nil)
244
-
245
- if @@hStartEvent == 0
246
- raise SystemCallError.new('CreateEvent', FFI.errno)
247
- end
248
-
249
- # Create the event to signal the service to stop.
250
- @@hStopEvent = CreateEvent(nil, true, false, nil)
251
-
252
- if @@hStopEvent == 0
253
- raise SystemCallError.new('CreateEvent', FFI.errno)
254
- end
255
-
256
- # Create the event to signal the service that stop has completed
257
- @@hStopCompletedEvent = CreateEvent(nil, true, false, nil)
258
-
259
- if @@hStopCompletedEvent == 0
260
- raise SystemCallError.new('CreateEvent', FFI.errno)
261
- end
262
-
263
- hThread = CreateThread(nil, 0, ThreadProc, Service_Main, 0, nil)
264
-
265
- if hThread == 0
266
- raise SystemCallError.new('CreateThread', FFI.errno)
267
- end
268
-
269
- events = FFI::MemoryPointer.new(:pointer, 2)
270
- events.put_pointer(0, FFI::Pointer.new(hThread))
271
- events.put_pointer(FFI::Pointer.size, FFI::Pointer.new(@@hStartEvent))
272
-
273
- while ((index = WaitForMultipleObjects(2, events, false, 1000)) == WAIT_TIMEOUT) do
274
- end
275
-
276
- if index == WAIT_FAILED
277
- raise SystemCallError.new('WaitForMultipleObjects', FFI.errno)
278
- end
279
-
280
- # The thread exited, so the show is off.
281
- if index == WAIT_OBJECT_0
282
- raise "Service_Main thread exited abnormally"
283
- end
284
-
285
- thr = Thread.new do
286
- begin
287
- while(WaitForSingleObject(@@hStopEvent, 10) == WAIT_TIMEOUT)
288
- # Check to see if anything interesting has been signaled
289
- case @@waiting_control_code
290
- when SERVICE_CONTROL_PAUSE
291
- service_pause() if respond_to?('service_pause')
292
- when SERVICE_CONTROL_CONTINUE
293
- service_resume() if respond_to?('service_resume')
294
- when SERVICE_CONTROL_INTERROGATE
295
- service_interrogate() if respond_to?('service_interrogate')
296
- when SERVICE_CONTROL_SHUTDOWN
297
- service_shutdown() if respond_to?('service_shutdown')
298
- when SERVICE_CONTROL_PARAMCHANGE
299
- service_paramchange() if respond_to?('service_paramchange')
300
- when SERVICE_CONTROL_NETBINDADD
301
- service_netbindadd() if respond_to?('service_netbindadd')
302
- when SERVICE_CONTROL_NETBINDREMOVE
303
- service_netbindremove() if respond_to?('service_netbindremove')
304
- when SERVICE_CONTROL_NETBINDENABLE
305
- service_netbindenable() if respond_to?('service_netbindenable')
306
- when SERVICE_CONTROL_NETBINDDISABLE
307
- service_netbinddisable() if respond_to?('service_netbinddisable')
308
- end
309
- @@waiting_control_code = IDLE_CONTROL_CODE
310
- end
311
-
312
- service_stop() if respond_to?('service_stop')
313
- ensure
314
- SetEvent(@@hStopCompletedEvent)
315
- end
316
- end
317
-
318
- if respond_to?('service_main')
319
- service_main(*@@Argv)
320
- end
321
-
322
- thr.join
323
- end
324
-
325
- # Returns the state of the service (as an constant integer) which can be any
326
- # of the service status constants, e.g. RUNNING, PAUSED, etc.
327
- #
328
- # This method is typically used within your service_main method to setup the
329
- # loop. For example:
330
- #
331
- # class MyDaemon < Daemon
332
- # def service_main
333
- # while state == RUNNING || state == PAUSED || state == IDLE
334
- # # Your main loop here
335
- # end
336
- # end
337
- # end
338
- #
339
- # See the Daemon#running? method for an abstraction of the above code.
340
- #
341
- def state
342
- @@dwServiceState
343
- end
344
-
345
- #
346
- # Returns whether or not the service is in a running state, i.e. the service
347
- # status is either RUNNING, PAUSED or IDLE.
348
- #
349
- # This is typically used within your service_main method to setup the main
350
- # loop. For example:
351
- #
352
- # class MyDaemon < Daemon
353
- # def service_main
354
- # while running?
355
- # # Your main loop here
356
- # end
357
- # end
358
- # end
359
- #
360
- def running?
361
- [SERVICE_RUNNING, SERVICE_PAUSED, 0].include?(@@dwServiceState)
362
- end
363
- end # Daemon
364
- end # Win32
1
+ require File.join(File.dirname(__FILE__), 'windows', 'helper')
2
+ require File.join(File.dirname(__FILE__), 'windows', 'constants')
3
+ require File.join(File.dirname(__FILE__), 'windows', 'structs')
4
+ require File.join(File.dirname(__FILE__), 'windows', 'functions')
5
+
6
+ require 'ffi'
7
+
8
+ # The Win32 module serves as a namespace only.
9
+ module Win32
10
+
11
+ # The Daemon class
12
+ class Daemon
13
+ include Windows::ServiceConstants
14
+ include Windows::Structs
15
+ include Windows::Functions
16
+
17
+ extend Windows::Structs
18
+ extend Windows::Functions
19
+
20
+ # The version of this library
21
+ VERSION = '0.8.6'
22
+
23
+ private
24
+
25
+ # Service is not running
26
+ STOPPED = SERVICE_STOPPED
27
+
28
+ # Service has received a start signal but is not yet running
29
+ START_PENDING = SERVICE_START_PENDING
30
+
31
+ # Service has received a stop signal but is not yet stopped
32
+ STOP_PENDING = SERVICE_STOP_PENDING
33
+
34
+ # Service is running
35
+ RUNNING = SERVICE_RUNNING
36
+
37
+ # Service has received a signal to resume but is not yet running
38
+ CONTINUE_PENDING = SERVICE_CONTINUE_PENDING
39
+
40
+ # Service has received a signal to pause but is not yet paused
41
+ PAUSE_PENDING = SERVICE_PAUSE_PENDING
42
+
43
+ # Service is paused
44
+ PAUSED = SERVICE_PAUSED
45
+
46
+ # Service controls
47
+
48
+ # Notifies service that it should stop
49
+ CONTROL_STOP = SERVICE_CONTROL_STOP
50
+
51
+ # Notifies service that it should pause
52
+ CONTROL_PAUSE = SERVICE_CONTROL_PAUSE
53
+
54
+ # Notifies service that it should resume
55
+ CONTROL_CONTINUE = SERVICE_CONTROL_CONTINUE
56
+
57
+ # Notifies service that it should return its current status information
58
+ CONTROL_INTERROGATE = SERVICE_CONTROL_INTERROGATE
59
+
60
+ # Notifies a service that its parameters have changed
61
+ CONTROL_PARAMCHANGE = SERVICE_CONTROL_PARAMCHANGE
62
+
63
+ # Notifies a service that there is a new component for binding
64
+ CONTROL_NETBINDADD = SERVICE_CONTROL_NETBINDADD
65
+
66
+ # Notifies a service that a component for binding has been removed
67
+ CONTROL_NETBINDREMOVE = SERVICE_CONTROL_NETBINDREMOVE
68
+
69
+ # Notifies a service that a component for binding has been enabled
70
+ CONTROL_NETBINDENABLE = SERVICE_CONTROL_NETBINDENABLE
71
+
72
+ # Notifies a service that a component for binding has been disabled
73
+ CONTROL_NETBINDDISABLE = SERVICE_CONTROL_NETBINDDISABLE
74
+
75
+ IDLE = 0
76
+
77
+ # Wraps SetServiceStatus.
78
+ SetTheServiceStatus = Proc.new do |dwCurrentState, dwWin32ExitCode,dwCheckPoint, dwWaitHint|
79
+ ss = SERVICE_STATUS.new # Current status of the service.
80
+
81
+ # Disable control requests until the service is started.
82
+ if dwCurrentState == SERVICE_START_PENDING
83
+ ss[:dwControlsAccepted] = 0
84
+ else
85
+ ss[:dwControlsAccepted] =
86
+ SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|
87
+ SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_SHUTDOWN
88
+ end
89
+
90
+ # Initialize ss structure.
91
+ ss[:dwServiceType] = SERVICE_WIN32_OWN_PROCESS
92
+ ss[:dwServiceSpecificExitCode] = 0
93
+ ss[:dwCurrentState] = dwCurrentState
94
+ ss[:dwWin32ExitCode] = dwWin32ExitCode
95
+ ss[:dwCheckPoint] = dwCheckPoint
96
+ ss[:dwWaitHint] = dwWaitHint
97
+
98
+ @@dwServiceState = dwCurrentState
99
+
100
+ # Send status of the service to the Service Controller.
101
+ if !SetServiceStatus(@@ssh, ss)
102
+ SetEvent(@@hStopEvent)
103
+ end
104
+ end
105
+
106
+ ERROR_CALL_NOT_IMPLEMENTED = 0x78
107
+
108
+ # Handles control signals from the service control manager.
109
+ Service_Ctrl_ex = Proc.new do |dwCtrlCode,dwEventType,lpEventData,lpContext|
110
+ @@waiting_control_code = dwCtrlCode;
111
+ return_value = NO_ERROR
112
+
113
+ begin
114
+ dwState = SERVICE_RUNNING
115
+
116
+ case dwCtrlCode
117
+ when SERVICE_CONTROL_STOP
118
+ dwState = SERVICE_STOP_PENDING
119
+ when SERVICE_CONTROL_SHUTDOWN
120
+ dwState = SERVICE_STOP_PENDING
121
+ when SERVICE_CONTROL_PAUSE
122
+ dwState = SERVICE_PAUSED
123
+ when SERVICE_CONTROL_CONTINUE
124
+ dwState = SERVICE_RUNNING
125
+ #else
126
+ # TODO: Handle other control codes? Retain the current state?
127
+ end
128
+
129
+ # Set the status of the service except on interrogation.
130
+ unless dwCtrlCode == SERVICE_CONTROL_INTERROGATE
131
+ SetTheServiceStatus.call(dwState, NO_ERROR, 0, 0)
132
+ end
133
+
134
+ # Tell service_main thread to stop.
135
+ if dwCtrlCode == SERVICE_CONTROL_STOP || dwCtrlCode == SERVICE_CONTROL_SHUTDOWN
136
+ if SetEvent(@@hStopEvent) == 0
137
+ SetTheServiceStatus.call(SERVICE_STOPPED, FFI.errno, 0, 0)
138
+ end
139
+ end
140
+ rescue
141
+ return_value = ERROR_CALL_NOT_IMPLEMENTED
142
+ end
143
+
144
+ return_value
145
+ end
146
+
147
+ # Called by the service control manager after the call to StartServiceCtrlDispatcher.
148
+ Service_Main = FFI::Function.new(:void, [:ulong, :pointer], :blocking => false) do |dwArgc,lpszArgv|
149
+ begin
150
+ # Obtain the name of the service.
151
+ if lpszArgv.address!=0
152
+ argv = lpszArgv.get_array_of_string(0,dwArgc)
153
+ lpszServiceName = argv[0]
154
+ else
155
+ lpszServiceName = ''
156
+ end
157
+
158
+ # Args passed to Service.start
159
+ if(dwArgc > 1)
160
+ @@Argv = argv[1..-1]
161
+ else
162
+ @@Argv = nil
163
+ end
164
+
165
+ # Register the service ctrl handler.
166
+ @@ssh = RegisterServiceCtrlHandlerEx(
167
+ lpszServiceName,
168
+ Service_Ctrl_ex,
169
+ nil
170
+ )
171
+
172
+ # No service to stop, no service handle to notify, nothing to do but exit.
173
+ return if @@ssh == 0
174
+
175
+ # The service has started.
176
+ SetTheServiceStatus.call(SERVICE_RUNNING, NO_ERROR, 0, 0)
177
+
178
+ SetEvent(@@hStartEvent)
179
+
180
+ # Main loop for the service.
181
+ while(WaitForSingleObject(@@hStopEvent, 1000) != WAIT_OBJECT_0) do
182
+ end
183
+
184
+ # Main loop for the service.
185
+ while(WaitForSingleObject(@@hStopCompletedEvent, 1000) != WAIT_OBJECT_0) do
186
+ end
187
+ ensure
188
+ # Stop the service.
189
+ SetTheServiceStatus.call(SERVICE_STOPPED, NO_ERROR, 0, 0)
190
+ end
191
+ end
192
+
193
+ ThreadProc = FFI::Function.new(:ulong,[:pointer]) do |lpParameter|
194
+ ste = FFI::MemoryPointer.new(SERVICE_TABLE_ENTRY, 2)
195
+
196
+ s = SERVICE_TABLE_ENTRY.new(ste[0])
197
+ s[:lpServiceName] = FFI::MemoryPointer.from_string('')
198
+ s[:lpServiceProc] = lpParameter
199
+
200
+ s = SERVICE_TABLE_ENTRY.new(ste[1])
201
+ s[:lpServiceName] = nil
202
+ s[:lpServiceProc] = nil
203
+
204
+ # No service to step, no service handle, no ruby exceptions, just terminate the thread..
205
+ if !StartServiceCtrlDispatcher(ste)
206
+ return 1
207
+ end
208
+
209
+ return 0
210
+ end
211
+
212
+ public
213
+
214
+ # This is a shortcut for Daemon.new + Daemon#mainloop.
215
+ #
216
+ def self.mainloop
217
+ self.new.mainloop
218
+ end
219
+
220
+ # This is the method that actually puts your code into a loop and allows it
221
+ # to run as a service. The code that is actually run while in the mainloop
222
+ # is what you defined in your own Daemon#service_main method.
223
+ #
224
+ def mainloop
225
+ @@waiting_control_code = IDLE_CONTROL_CODE
226
+ @@dwServiceState = 0
227
+
228
+ # Redirect STDIN, STDOUT and STDERR to the NUL device if they're still
229
+ # associated with a tty. This helps newbs avoid Errno::EBADF errors.
230
+ STDIN.reopen('NUL') if STDIN.isatty
231
+ STDOUT.reopen('NUL') if STDOUT.isatty
232
+ STDERR.reopen('NUL') if STDERR.isatty
233
+
234
+ # Calling init here so that init failures never even tries to start the
235
+ # service. Of course that means that init methods must be very quick
236
+ # because the SCM will be receiving no START_PENDING messages while
237
+ # init's running.
238
+ #
239
+ # TODO: Fix?
240
+ service_init() if respond_to?('service_init')
241
+
242
+ # Create the event to signal the service to start.
243
+ @@hStartEvent = CreateEvent(nil, true, false, nil)
244
+
245
+ if @@hStartEvent == 0
246
+ raise SystemCallError.new('CreateEvent', FFI.errno)
247
+ end
248
+
249
+ # Create the event to signal the service to stop.
250
+ @@hStopEvent = CreateEvent(nil, true, false, nil)
251
+
252
+ if @@hStopEvent == 0
253
+ raise SystemCallError.new('CreateEvent', FFI.errno)
254
+ end
255
+
256
+ # Create the event to signal the service that stop has completed
257
+ @@hStopCompletedEvent = CreateEvent(nil, true, false, nil)
258
+
259
+ if @@hStopCompletedEvent == 0
260
+ raise SystemCallError.new('CreateEvent', FFI.errno)
261
+ end
262
+
263
+ hThread = CreateThread(nil, 0, ThreadProc, Service_Main, 0, nil)
264
+
265
+ if hThread == 0
266
+ raise SystemCallError.new('CreateThread', FFI.errno)
267
+ end
268
+
269
+ events = FFI::MemoryPointer.new(:pointer, 2)
270
+ events.put_pointer(0, FFI::Pointer.new(hThread))
271
+ events.put_pointer(FFI::Pointer.size, FFI::Pointer.new(@@hStartEvent))
272
+
273
+ while ((index = WaitForMultipleObjects(2, events, false, 1000)) == WAIT_TIMEOUT) do
274
+ end
275
+
276
+ if index == WAIT_FAILED
277
+ raise SystemCallError.new('WaitForMultipleObjects', FFI.errno)
278
+ end
279
+
280
+ # The thread exited, so the show is off.
281
+ if index == WAIT_OBJECT_0
282
+ raise "Service_Main thread exited abnormally"
283
+ end
284
+
285
+ thr = Thread.new do
286
+ begin
287
+ while(WaitForSingleObject(@@hStopEvent, 10) == WAIT_TIMEOUT)
288
+ # Check to see if anything interesting has been signaled
289
+ case @@waiting_control_code
290
+ when SERVICE_CONTROL_PAUSE
291
+ service_pause() if respond_to?('service_pause')
292
+ when SERVICE_CONTROL_CONTINUE
293
+ service_resume() if respond_to?('service_resume')
294
+ when SERVICE_CONTROL_INTERROGATE
295
+ service_interrogate() if respond_to?('service_interrogate')
296
+ when SERVICE_CONTROL_SHUTDOWN
297
+ service_shutdown() if respond_to?('service_shutdown')
298
+ when SERVICE_CONTROL_PARAMCHANGE
299
+ service_paramchange() if respond_to?('service_paramchange')
300
+ when SERVICE_CONTROL_NETBINDADD
301
+ service_netbindadd() if respond_to?('service_netbindadd')
302
+ when SERVICE_CONTROL_NETBINDREMOVE
303
+ service_netbindremove() if respond_to?('service_netbindremove')
304
+ when SERVICE_CONTROL_NETBINDENABLE
305
+ service_netbindenable() if respond_to?('service_netbindenable')
306
+ when SERVICE_CONTROL_NETBINDDISABLE
307
+ service_netbinddisable() if respond_to?('service_netbinddisable')
308
+ end
309
+ @@waiting_control_code = IDLE_CONTROL_CODE
310
+ end
311
+
312
+ service_stop() if respond_to?('service_stop')
313
+ ensure
314
+ SetEvent(@@hStopCompletedEvent)
315
+ end
316
+ end
317
+
318
+ if respond_to?('service_main')
319
+ service_main(*@@Argv)
320
+ end
321
+
322
+ thr.join
323
+ end
324
+
325
+ # Returns the state of the service (as an constant integer) which can be any
326
+ # of the service status constants, e.g. RUNNING, PAUSED, etc.
327
+ #
328
+ # This method is typically used within your service_main method to setup the
329
+ # loop. For example:
330
+ #
331
+ # class MyDaemon < Daemon
332
+ # def service_main
333
+ # while state == RUNNING || state == PAUSED || state == IDLE
334
+ # # Your main loop here
335
+ # end
336
+ # end
337
+ # end
338
+ #
339
+ # See the Daemon#running? method for an abstraction of the above code.
340
+ #
341
+ def state
342
+ @@dwServiceState
343
+ end
344
+
345
+ #
346
+ # Returns whether or not the service is in a running state, i.e. the service
347
+ # status is either RUNNING, PAUSED or IDLE.
348
+ #
349
+ # This is typically used within your service_main method to setup the main
350
+ # loop. For example:
351
+ #
352
+ # class MyDaemon < Daemon
353
+ # def service_main
354
+ # while running?
355
+ # # Your main loop here
356
+ # end
357
+ # end
358
+ # end
359
+ #
360
+ def running?
361
+ [SERVICE_RUNNING, SERVICE_PAUSED, 0].include?(@@dwServiceState)
362
+ end
363
+ end # Daemon
364
+ end # Win32