Running as a service

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Running as a service

Ian Bartholomew-21
I've got a couple of apps that run permanently (they monitor and log various
things) and which are deployed in the "normal" way but have no actual need
for a UI.

I was thinking about rewriting them to run as a console app so they could (I
think) then be run as a service.

Has anyone been down this route recently (on XP) and could I get some
feedback on how feasible/easy  it will be.  If it makes any difference the
apps will need to use Sockets and the IStream classes (for web access).

TIA

--
Ian

Use the Reply-To address to contact me (limited validity).
Mail sent to the From address is ignored.


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

John Rubier
Ian Bartholomew wrote:

> I've got a couple of apps that run permanently (they monitor and log various
> things) and which are deployed in the "normal" way but have no actual need
> for a UI.
>
> I was thinking about rewriting them to run as a console app so they could (I
> think) then be run as a service.
>
> Has anyone been down this route recently (on XP) and could I get some
> feedback on how feasible/easy  it will be.  If it makes any difference the
> apps will need to use Sockets and the IStream classes (for web access).
>
> TIA
>
> --
> Ian
>
> Use the Reply-To address to contact me (limited validity).
> Mail sent to the From address is ignored.

Hi Ian,
I'm working on an app right now that I was planning to run as a
service. While I can't vouch 100% for this info until I actually do it,
here's this gist of what I've seen.

Under XP (and I believe W2k) as service is allowed to have a GUI if the
"Interactive" flag is specified.
It seems to me that an Dolphin executable could work as a "real" NT
service. Here is a nice straightforward explaination:
http://www.commsoft.com/services.html

Me, I was planning on taking the easy way out and use XYZService:
http://www.codeproject.com/system/xyntservice.asp

However, since I know you're going to write a cool
NTServiceSessionManager class I'll probably just wait for that ;)

Take care,

John


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Martin Rubi
In reply to this post by Ian Bartholomew-21
Hello Ian.
I've just made a quick (and ugly) service which opens a Shell and serves
objects using rSt, and it seems to be able to do both things (after starting
the service, I was able to see the shell and create instances of
OrderedCollection from a client image in the same machine). So perhaps it's
not even necesary to create a console application.
It runs under winXP and Dolphin 5.1.4.
It uses Bill's ServiceControl package, the srvany.exe program and a custom
ServiceSessionManager I've made, and it seems pretty easy to convert a
regular application into a service with all of these things. The
ServiceSessionManager is a helper to allow the deployed executable to be
installed and uninstalled as a service and to keep the service alive when no
views are opened.
The only problem I had a couple of months ago with a service in winXP was
when the service had to access remote files. It was a permissions problem,
and we had to write our own srvany.exe like application which creates the
application process impersonating a windows user with enough permissions to
open the files we needed. It was as ugly as it sounds, and it's not related
to Dolphin in particular, but to all services.

Another issue could be the service termination. My guess is that srvany.exe
just kills the application process to stop the service (I could get away
with it so far).

So, in my opinion, using Bill's package and svrany.exe, it shouldn't be too
much work to make an existing application become a service.

best regards
martin

"Ian Bartholomew" <[hidden email]> escribió en el mensaje
news:[hidden email]...

> I've got a couple of apps that run permanently (they monitor and log
> various things) and which are deployed in the "normal" way but have no
> actual need for a UI.
>
> I was thinking about rewriting them to run as a console app so they could
> (I think) then be run as a service.
>
> Has anyone been down this route recently (on XP) and could I get some
> feedback on how feasible/easy  it will be.  If it makes any difference the
> apps will need to use Sockets and the IStream classes (for web access).
>
> TIA
>
> --
> Ian
>
> Use the Reply-To address to contact me (limited validity).
> Mail sent to the From address is ignored.
>


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

John Rubier
In reply to this post by John Rubier
John Rubier wrote:

> Ian Bartholomew wrote:
> > I've got a couple of apps that run permanently (they monitor and log various
> > things) and which are deployed in the "normal" way but have no actual need
> > for a UI.
> >
> > I was thinking about rewriting them to run as a console app so they could (I
> > think) then be run as a service.
> >
> > Has anyone been down this route recently (on XP) and could I get some
> > feedback on how feasible/easy  it will be.  If it makes any difference the
> > apps will need to use Sockets and the IStream classes (for web access).
> >
> > TIA
> >
> > --
> > Ian
> >
> > Use the Reply-To address to contact me (limited validity).
> > Mail sent to the From address is ignored.
>
> Hi Ian,
> I'm working on an app right now that I was planning to run as a
> service. While I can't vouch 100% for this info until I actually do it,
> here's this gist of what I've seen.
>
> Under XP (and I believe W2k) as service is allowed to have a GUI if the
> "Interactive" flag is specified.
> It seems to me that an Dolphin executable could work as a "real" NT
> service. Here is a nice straightforward explaination:
> http://www.commsoft.com/services.html
>
> Me, I was planning on taking the easy way out and use XYZService:
> http://www.codeproject.com/system/xyntservice.asp
>
> However, since I know you're going to write a cool
> NTServiceSessionManager class I'll probably just wait for that ;)
>
> Take care,
>
> John

Oh, Bill Schwan has a package in his goodies called ServiceControl that
may do everything we need. It works with SrvAny.exe.


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Ian Bartholomew-21
In reply to this post by John Rubier
Thanks to John and Martin for the useful replies (and apologies to Bill for
forgetting about his ServiceControl package).

I shall have a read and a play and see what happens :=)

--
Ian

Use the Reply-To address to contact me (limited validity).
Mail sent to the From address is ignored.


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Martin Rubi
In reply to this post by John Rubier
> It seems to me that an Dolphin executable could work as a "real" NT
> service. Here is a nice straightforward explaination:
> http://www.commsoft.com/services.html

I don't think it's possible, at least in D5, unless there is a way to
indefinitely block an ExternalCallback without blocking the main process and
without losing the ability to receive more external callbacks. I couldn't
find a way to do this.

best regards
martin


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Schwab,Wilhelm K
In reply to this post by John Rubier
Ian,

> Thanks to John and Martin for the useful replies (and apologies to Bill for
> forgetting about his ServiceControl package).

That makes two of us ;)  I hope it helps.  Please feel free to use it as
a base for something better if the mood strikes.


> I shall have a read and a play and see what happens :=)

I obviously reached the conclusion that srvany was the only option for
D5, which is not to say that is indeed the case.

I recall more of my experiments with Squeak as a service.  Either way,
be sure you can log out and back in before claiming success, especially
if you allow/require the service to be directly interactive.  A service
is probably best run as a console app with a separate GUI for control.
Apache does a very nice job of it, but that's probably not much help.

As mentioned elsewhere in this thread, sockets and COM could be
problematic.  In D6, you have overlapped sockets available, and they
might work in this setting.

Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Ian Bartholomew-21
In reply to this post by John Rubier
Just a brief update in case anyone is interested.

I converted two of my apps to work as Console apps without too much trouble
although, as documented in another thread, some aspects were a bit
confusing.

I decided on, initially at least, taking the path of least resistance and
downloaded the XYZService mentioned by John.  It worked very well on my
development machine, took about 5 mins to set up and get both console apps
running as services.

It took a bit longer when I transferred things to the machine that they were
intended to run on.  It turns out that either XYZService, or Windows itself,
didn't like the apps residing in a shared folder (All Users/Documents) and
silently ignored the request to run them.  Once that was sorted (I just
moved the apps to a normal folder but left their log files in the shared
one) everything works fine (touch wood).

I'll have a look at a more Dolphinish way of doing it now..

--
Ian

Use the Reply-To address to contact me (limited validity).
Mail sent to the From address is ignored.


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

John Rubier
In reply to this post by Ian Bartholomew-21
Ok, it appears that D6 *can* work as a native NT service.
Here is a very primitive and ugly POC.
The key appears to be marking the AdvApi setServiceStatus call as
overlapped *and* forking the method that calls it. Don't know if this is
possible in D5.
It *does* survive a login/logout as Bill suggested as a test.
I do not have GUI as from what I can tell, one is better off having the
GUI a separate app and using named pipes to connect to the service.
Apparently an "interactive" service can only show on the first session.
Problematic with Terminal Services and the related Fast User Switching.
Use Debug view to view the output.

Following is the text of the package. It can also be downloaded (for the
time being) at: http://www.bigrube.com/NTService.pac

I created a service under XP with:
sc create "aaaTestService" binPath= c:\aaaTemp\NTService.exe"

Apologies in advance if your hard drive gets wiped using this or you get
a migraine or stroke from viewing the code ;)

Take care,

John

*********Cut here
| package |
package := Package name: 'NTService'.
package paxVersion: 1;
        basicComment: ''.

package imageStripperBytes: (ByteArray fromBase64String:
'IVNUQiAzIEYPDQAEAAAASW1hZ2VTdHJpcHBlcgAAAABSAAAACQAAAE5UU2VydmljZVIAAAAYAAAA
QzpcYWFhVGVtcFxOVFNlcnZpY2UuZXhlmgAAAFIAAAAJAAAATlRTZXJ2aWNlUgAAABcAAABOVFNl
cnZpY2VTZXNzaW9uTWFuYWdlcu+/JQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA').

package classNames
        add: #NTServiceSessionManager;
        add: #SERVICE_STATUS;
        add: #SERVICE_TABLE_ENTRY;
        yourself.

package methodNames
        add: #AdvApiLibrary -> #registerServiceCtrlHandler:lpHandlerProc:;
        add: #AdvApiLibrary -> #setServiceStatus:lpHandlerProc:;
        add: #AdvApiLibrary -> #startServiceCtrlDispatcher:;
        yourself.

package binaryGlobalNames: (Set new
        yourself).

package globalAliases: (Set new
        yourself).

package setPrerequisites: (IdentitySet new
        add: '..\Object Arts\Dolphin\Base\Dolphin';
        add: '..\Object Arts\Dolphin\MVP\Base\Dolphin MVP Base';
        add: '..\Object Arts\Dolphin\Lagoon\Lagoon Image Stripper';
        yourself).

package!

"Class Definitions"!

ExternalStructure subclass: #SERVICE_STATUS
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        classInstanceVariableNames: ''!
ExternalStructure subclass: #SERVICE_TABLE_ENTRY
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        classInstanceVariableNames: ''!
RuntimeSessionManager subclass: #NTServiceSessionManager
        instanceVariableNames: 'tickCount tickCountMutex status
serviceMainCallback serviceHandlerCallback serviceStopMutex
serviceStatusHandle'
        classVariableNames: ''
        poolDictionaries: ''
        classInstanceVariableNames: ''!

"Global Aliases"!


"Loose Methods"!

!AdvApiLibrary methodsFor!

registerServiceCtrlHandler: aString lpHandlerProc: aProcPointer
        <stdcall: dword RegisterServiceCtrlHandlerA lpvoid lpvoid>
        ^self invalidCall!

setServiceStatus: aServiceHandle lpHandlerProc: aServiceStatus
        <stdcall: bool SetServiceStatus dword lpvoid>
        ^self invalidCall!

startServiceCtrlDispatcher: aServiceTable
        <overlap stdcall: bool StartServiceCtrlDispatcherA lpvoid>
        ^self invalidCall! !
!AdvApiLibrary categoriesFor:
#registerServiceCtrlHandler:lpHandlerProc:!must not strip!public!win32
functions-events! !
!AdvApiLibrary categoriesFor: #setServiceStatus:lpHandlerProc:!must not
strip!public!win32 functions-events! !
!AdvApiLibrary categoriesFor: #startServiceCtrlDispatcher:!must not
strip!public!win32 functions-events! !

"End of package definition"!

"Source Globals"!

"Classes"!

SERVICE_STATUS guid: (GUID fromString:
'{2BC2B412-26D7-481E-A5DF-BD933DD96014}')!
SERVICE_STATUS comment: ''!
!SERVICE_STATUS categoriesForClass!External-Data-Structured! !
!SERVICE_STATUS class methodsFor!

defineFields
        "Define the fields of the SERVICE_STATUS structure.
                SERVICE_STATUS compileDefinition
       
        typedef struct _SERVICE_STATUS {
                DWORD dwServiceType;
                DWORD dwCurrentState;
                DWORD dwControlsAccepted;
                DWORD dwWin32ExitCode;
                DWORD dwServiceSpecificExitCode;
                DWORD dwCheckPoint;
                DWORD dwWaitHint;
        } SERVICE_STATUS, *LPSERVICE_STATUS;

        "

        self
                defineField: #dwServiceType type: DWORDField new;
                defineField: #dwCurrentState type: DWORDField new;
                defineField: #dwControlsAccepted type: DWORDField new;
                defineField: #dwWin32ExitCode type: DWORDField new;
                defineField: #dwServiceSpecificExitCode type: DWORDField new;
                defineField: #dwCheckPoint type: DWORDField new;
                defineField: #dwWaitHint type: DWORDField new! !
!SERVICE_STATUS class categoriesFor: #defineFields!**auto
generated**!initializing!public! !

SERVICE_TABLE_ENTRY guid: (GUID fromString:
'{A65B05B4-0CEC-4E24-83CF-413C89052787}')!
SERVICE_TABLE_ENTRY comment: ''!
!SERVICE_TABLE_ENTRY categoriesForClass!External-Data-Structured! !
!SERVICE_TABLE_ENTRY methodsFor!

lpServiceName
        "Answer the receiver's lpServiceName field as a Smalltalk object."

        ^String fromAddress: (bytes sdwordAtOffset: 0)!

lpServiceName: anObject
        "Set the receiver's lpServiceName field to the value of anObject."

        bytes dwordAtOffset: 0 put: anObject yourAddress!

lpServiceProc
        "Answer the receiver's lpServiceProc field as a Smalltalk object."

        ^(bytes dwordAtOffset: 4) asExternalAddress!

lpServiceProc: anObject
        "Set the receiver's lpServiceProc field to the value of anObject."

        bytes dwordAtOffset: 4 put: anObject! !
!SERVICE_TABLE_ENTRY categoriesFor: #lpServiceName!**compiled
accessors**!public! !
!SERVICE_TABLE_ENTRY categoriesFor: #lpServiceName:!**compiled
accessors**!public! !
!SERVICE_TABLE_ENTRY categoriesFor: #lpServiceProc!**compiled
accessors**!public! !
!SERVICE_TABLE_ENTRY categoriesFor: #lpServiceProc:!**compiled
accessors**!public! !

!SERVICE_TABLE_ENTRY class methodsFor!

defineFields
        "Define the fields of the SERVICE_TABLE_ENTRY structure.
                SERVICE_TABLE_ENTRY compileDefinition
       
                typedef struct _SERVICE_TABLE_ENTRY {
   LPTSTR lpServiceName;
   LPSERVICE_MAIN_FUNCTION lpServiceProc;
} SERVICE_TABLE_ENTRY,
  *LPSERVICE_TABLE_ENTRY;

        "

        self
                defineField: #lpServiceName type:  (PointerField type: String);
                defineField: #lpServiceProc type: LPVOIDField new.
! !
!SERVICE_TABLE_ENTRY class categoriesFor: #defineFields!**auto
generated**!initializing!public! !

NTServiceSessionManager guid: (GUID fromString:
'{D7D0A391-CA82-47C6-AD7F-AD459669BC0F}')!
NTServiceSessionManager comment: ''!
!NTServiceSessionManager categoriesForClass!Unclassified! !
!NTServiceSessionManager methodsFor!

keepAlive
        "SessionManager current trace: 'keepAlive called by inputstate'."
        ^self!

main


        tickCount := 0.
        tickCountMutex := Mutex new.
        [self registerService] forkAt: Processor userBackgroundPriority.
        SessionManager current trace: 'Post registerService fork'.
        [
        [self tick.
        Processor sleep: 1000] repeat] forkAt: Processor userBackgroundPriority
        "ServiceShell showOn: tickCount."!

registerService
        | st rc ste  |
st := StructureArray length:2 elementClass: SERVICE_TABLE_ENTRY.
ste := SERVICE_TABLE_ENTRY new.
ste lpServiceName: ('' asParameter ).

serviceMainCallback := ExternalCallback receiver: self selector:
#serviceMain:argv: descriptor: ##(ExternalDescriptor argumentTypes:
'dword lpvoid') .
ste lpServiceProc: (serviceMainCallback asParameter yourAddress ).
st at:1 put: ste .
SessionManager current trace: 'Calling startServiceCtrlDispatcher'.
rc := AdvApiLibrary default startServiceCtrlDispatcher: st.
SessionManager current trace: 'Return code:',(rc displayString).
SessionManager current trace: 'Error:',((KernelLibrary default
getLastError ) displayString).!

serviceHandler: controlCode
        | status rc |
        SessionManager current trace: 'serviceHandler Callback' , controlCode
displayString.
        controlCode = 1
                ifTrue:
                        [status := SERVICE_STATUS new.
                        status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
                        status dwCurrentState: 3. "SERVICE_STOP_PENDING"
                        status dwControlsAccepted: 7. "SERVICE_ACCEPT_PAUSE_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP"
                        status dwWin32ExitCode: 0.
                        status dwServiceSpecificExitCode: 0.
                        status dwCheckPoint: 0.
                        status dwWaitHint: 0.
                        rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
lpHandlerProc: status.
                        SessionManager current trace: 'serviceHandler called
setServiceStatus:' , rc displayString.
                        rc
                                ifFalse:
                                        [SessionManager current
                                                trace: 'setServiceStatus error:' , KernelLibrary default
getLastError displayString].
                        self stopService] ifFalse: [ (controlCode  = 2) ifTrue: [status :=
SERVICE_STATUS new.
                        status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
                        status dwCurrentState: 3. "SERVICE_STOP_PENDING"
                        status dwControlsAccepted: 7. "SERVICE_ACCEPT_PAUSE_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP"
                        status dwWin32ExitCode: 0.
                        status dwServiceSpecificExitCode: 0.
                        status dwCheckPoint: 0.
                        status dwWaitHint: 0.
                        rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
lpHandlerProc: status.
                        SessionManager current trace: 'serviceHandler called
setServiceStatus:' , rc displayString.
                        rc
                                ifFalse:
                                        [SessionManager current
                                                trace: 'setServiceStatus error:' , KernelLibrary default
getLastError displayString]] ].
^nil!

serviceMain: argc argv: ptrArgv
        | rc status |
        SessionManager current trace: 'serviceMain Callback'.
        "Register handler"
        serviceHandlerCallback := ExternalCallback
                                receiver: self
                                selector: #serviceHandler:
                                descriptor: ##(ExternalDescriptor argumentTypes: 'dword').
        serviceStopMutex := Semaphore new.
        serviceStatusHandle := AdvApiLibrary default
registerServiceCtrlHandler: '' asParameter
                                lpHandlerProc: serviceHandlerCallback asParameter yourAddress.
        SessionManager current trace: 'serviceStatus handle' ,
serviceStatusHandle displayString.
        status := SERVICE_STATUS new.
        status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
        status dwCurrentState: 4. "SERVICE_RUNNING"
        status dwControlsAccepted: 5. "SERVICE_ACCEPT_SHUTDOWN |
SERVICE_ACCEPT_STOP"
        status dwWin32ExitCode: 0.
        status dwServiceSpecificExitCode: 0.
        status dwCheckPoint: 0.
        status dwWaitHint: 0.
        rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
lpHandlerProc: (status  ).
        SessionManager current trace: 'serviceMain called setServiceStatus:' ,
rc displayString.
        rc
                ifFalse:
                        [SessionManager current
                                trace: 'setServiceStatus error:' , KernelLibrary default
getLastError displayString].
        "block until service stopped"
        "serviceStopMutex wait."
        ^0!

stopService
        | status rc |
        status := SERVICE_STATUS new.
                        status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
                        status dwCurrentState: 1. "SERVICE_STOPPED"
                        status dwControlsAccepted: 7. "SERVICE_ACCEPT_PAUSE_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP"
                        status dwWin32ExitCode: 0.
                        status dwServiceSpecificExitCode: 0.
                        status dwCheckPoint: 0.
                        status dwWaitHint: 0.
                        rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
lpHandlerProc: status.
                        SessionManager current trace: 'serviceHandler called
setServiceStatus:' , rc displayString.
                        rc
                                ifFalse:
                                        [SessionManager current
                                                trace: 'setServiceStatus error:' , KernelLibrary default
getLastError displayString].
                        self exit.!

tick
        SessionManager current trace: 'tick'.
        tickCountMutex critical: [tickCount := tickCount + 1]! !
!NTServiceSessionManager categoriesFor: #keepAlive!public! !
!NTServiceSessionManager categoriesFor: #main!public! !
!NTServiceSessionManager categoriesFor: #registerService!public! !
!NTServiceSessionManager categoriesFor: #serviceHandler:!public! !
!NTServiceSessionManager categoriesFor: #serviceMain:argv:!public! !
!NTServiceSessionManager categoriesFor: #stopService!public! !
!NTServiceSessionManager categoriesFor: #tick!public! !

"Binary Globals"!


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Chris Uppal-3
In reply to this post by Ian Bartholomew-21
Ian Bartholomew wrote:

> Has anyone been down this route recently (on XP) and could I get some
> feedback on how feasible/easy  it will be.  If it makes any difference the
> apps will need to use Sockets and the IStream classes (for web access).

Just a small warning about running a Dolphin app, with an event loop, as a
service.

There's a potential security problem with privilege-elevation using a "shatter"
attack.  That's where the attacker sends Windows messages to the input queue of
another application running with elevated/different privileges, causing it to
run code of the attacker's choice.  Google will find more information.

It's an elegant and easy technique which /might/ be applicable to the sort of
scenario we have been talking about in this thread.  I doubt whether it's an
issue for Ian, but if anyone is thinking about creating services to run in more
hostile environments, then it's something to consider.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Martin Rubi
In reply to this post by John Rubier
Interesting !

A question: would you, John, and Bill mind if a take code and ideas from
both of your packages and merge them with some of my stuff and try to make a
fully featured service framework with all of it ? Or, if you prefer, I can
send my stuff to you.

best regards
martin

"John" <[hidden email]> escribió en el mensaje
news:%h6lg.7266$[hidden email]...

> Ok, it appears that D6 *can* work as a native NT service.
> Here is a very primitive and ugly POC.
> The key appears to be marking the AdvApi setServiceStatus call as
> overlapped *and* forking the method that calls it. Don't know if this is
> possible in D5.
> It *does* survive a login/logout as Bill suggested as a test.
> I do not have GUI as from what I can tell, one is better off having the
> GUI a separate app and using named pipes to connect to the service.
> Apparently an "interactive" service can only show on the first session.
> Problematic with Terminal Services and the related Fast User Switching.
> Use Debug view to view the output.
>
> Following is the text of the package. It can also be downloaded (for the
> time being) at: http://www.bigrube.com/NTService.pac
>
> I created a service under XP with:
> sc create "aaaTestService" binPath= c:\aaaTemp\NTService.exe"
>
> Apologies in advance if your hard drive gets wiped using this or you get a
> migraine or stroke from viewing the code ;)
>
> Take care,
>
> John
>
> *********Cut here
> | package |
> package := Package name: 'NTService'.
> package paxVersion: 1;
> basicComment: ''.
>
> package imageStripperBytes: (ByteArray fromBase64String:
> 'IVNUQiAzIEYPDQAEAAAASW1hZ2VTdHJpcHBlcgAAAABSAAAACQAAAE5UU2VydmljZVIAAAAYAAAA
> QzpcYWFhVGVtcFxOVFNlcnZpY2UuZXhlmgAAAFIAAAAJAAAATlRTZXJ2aWNlUgAAABcAAABOVFNl
> cnZpY2VTZXNzaW9uTWFuYWdlcu+/JQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
> AAAAAAAA').
>
> package classNames
> add: #NTServiceSessionManager;
> add: #SERVICE_STATUS;
> add: #SERVICE_TABLE_ENTRY;
> yourself.
>
> package methodNames
> add: #AdvApiLibrary -> #registerServiceCtrlHandler:lpHandlerProc:;
> add: #AdvApiLibrary -> #setServiceStatus:lpHandlerProc:;
> add: #AdvApiLibrary -> #startServiceCtrlDispatcher:;
> yourself.
>
> package binaryGlobalNames: (Set new
> yourself).
>
> package globalAliases: (Set new
> yourself).
>
> package setPrerequisites: (IdentitySet new
> add: '..\Object Arts\Dolphin\Base\Dolphin';
> add: '..\Object Arts\Dolphin\MVP\Base\Dolphin MVP Base';
> add: '..\Object Arts\Dolphin\Lagoon\Lagoon Image Stripper';
> yourself).
>
> package!
>
> "Class Definitions"!
>
> ExternalStructure subclass: #SERVICE_STATUS
> instanceVariableNames: ''
> classVariableNames: ''
> poolDictionaries: ''
> classInstanceVariableNames: ''!
> ExternalStructure subclass: #SERVICE_TABLE_ENTRY
> instanceVariableNames: ''
> classVariableNames: ''
> poolDictionaries: ''
> classInstanceVariableNames: ''!
> RuntimeSessionManager subclass: #NTServiceSessionManager
> instanceVariableNames: 'tickCount tickCountMutex status
> serviceMainCallback serviceHandlerCallback serviceStopMutex
> serviceStatusHandle'
> classVariableNames: ''
> poolDictionaries: ''
> classInstanceVariableNames: ''!
>
> "Global Aliases"!
>
>
> "Loose Methods"!
>
> !AdvApiLibrary methodsFor!
>
> registerServiceCtrlHandler: aString lpHandlerProc: aProcPointer
> <stdcall: dword RegisterServiceCtrlHandlerA lpvoid lpvoid>
> ^self invalidCall!
>
> setServiceStatus: aServiceHandle lpHandlerProc: aServiceStatus
> <stdcall: bool SetServiceStatus dword lpvoid>
> ^self invalidCall!
>
> startServiceCtrlDispatcher: aServiceTable
> <overlap stdcall: bool StartServiceCtrlDispatcherA lpvoid>
> ^self invalidCall! !
> !AdvApiLibrary categoriesFor:
> #registerServiceCtrlHandler:lpHandlerProc:!must not strip!public!win32
> functions-events! !
> !AdvApiLibrary categoriesFor: #setServiceStatus:lpHandlerProc:!must not
> strip!public!win32 functions-events! !
> !AdvApiLibrary categoriesFor: #startServiceCtrlDispatcher:!must not
> strip!public!win32 functions-events! !
>
> "End of package definition"!
>
> "Source Globals"!
>
> "Classes"!
>
> SERVICE_STATUS guid: (GUID fromString:
> '{2BC2B412-26D7-481E-A5DF-BD933DD96014}')!
> SERVICE_STATUS comment: ''!
> !SERVICE_STATUS categoriesForClass!External-Data-Structured! !
> !SERVICE_STATUS class methodsFor!
>
> defineFields
> "Define the fields of the SERVICE_STATUS structure.
> SERVICE_STATUS compileDefinition
>
> typedef struct _SERVICE_STATUS {
> DWORD dwServiceType;
> DWORD dwCurrentState;
> DWORD dwControlsAccepted;
> DWORD dwWin32ExitCode;
> DWORD dwServiceSpecificExitCode;
> DWORD dwCheckPoint;
> DWORD dwWaitHint;
> } SERVICE_STATUS, *LPSERVICE_STATUS;
>
> "
>
> self
> defineField: #dwServiceType type: DWORDField new;
> defineField: #dwCurrentState type: DWORDField new;
> defineField: #dwControlsAccepted type: DWORDField new;
> defineField: #dwWin32ExitCode type: DWORDField new;
> defineField: #dwServiceSpecificExitCode type: DWORDField new;
> defineField: #dwCheckPoint type: DWORDField new;
> defineField: #dwWaitHint type: DWORDField new! !
> !SERVICE_STATUS class categoriesFor: #defineFields!**auto
> generated**!initializing!public! !
>
> SERVICE_TABLE_ENTRY guid: (GUID fromString:
> '{A65B05B4-0CEC-4E24-83CF-413C89052787}')!
> SERVICE_TABLE_ENTRY comment: ''!
> !SERVICE_TABLE_ENTRY categoriesForClass!External-Data-Structured! !
> !SERVICE_TABLE_ENTRY methodsFor!
>
> lpServiceName
> "Answer the receiver's lpServiceName field as a Smalltalk object."
>
> ^String fromAddress: (bytes sdwordAtOffset: 0)!
>
> lpServiceName: anObject
> "Set the receiver's lpServiceName field to the value of anObject."
>
> bytes dwordAtOffset: 0 put: anObject yourAddress!
>
> lpServiceProc
> "Answer the receiver's lpServiceProc field as a Smalltalk object."
>
> ^(bytes dwordAtOffset: 4) asExternalAddress!
>
> lpServiceProc: anObject
> "Set the receiver's lpServiceProc field to the value of anObject."
>
> bytes dwordAtOffset: 4 put: anObject! !
> !SERVICE_TABLE_ENTRY categoriesFor: #lpServiceName!**compiled
> accessors**!public! !
> !SERVICE_TABLE_ENTRY categoriesFor: #lpServiceName:!**compiled
> accessors**!public! !
> !SERVICE_TABLE_ENTRY categoriesFor: #lpServiceProc!**compiled
> accessors**!public! !
> !SERVICE_TABLE_ENTRY categoriesFor: #lpServiceProc:!**compiled
> accessors**!public! !
>
> !SERVICE_TABLE_ENTRY class methodsFor!
>
> defineFields
> "Define the fields of the SERVICE_TABLE_ENTRY structure.
> SERVICE_TABLE_ENTRY compileDefinition
>
> typedef struct _SERVICE_TABLE_ENTRY {
>   LPTSTR lpServiceName;
>   LPSERVICE_MAIN_FUNCTION lpServiceProc;
> } SERVICE_TABLE_ENTRY,
>  *LPSERVICE_TABLE_ENTRY;
>
> "
>
> self
> defineField: #lpServiceName type:  (PointerField type: String);
> defineField: #lpServiceProc type: LPVOIDField new.
> ! !
> !SERVICE_TABLE_ENTRY class categoriesFor: #defineFields!**auto
> generated**!initializing!public! !
>
> NTServiceSessionManager guid: (GUID fromString:
> '{D7D0A391-CA82-47C6-AD7F-AD459669BC0F}')!
> NTServiceSessionManager comment: ''!
> !NTServiceSessionManager categoriesForClass!Unclassified! !
> !NTServiceSessionManager methodsFor!
>
> keepAlive
> "SessionManager current trace: 'keepAlive called by inputstate'."
> ^self!
>
> main
>
>
> tickCount := 0.
> tickCountMutex := Mutex new.
> [self registerService] forkAt: Processor userBackgroundPriority.
> SessionManager current trace: 'Post registerService fork'.
> [
> [self tick.
> Processor sleep: 1000] repeat] forkAt: Processor userBackgroundPriority
> "ServiceShell showOn: tickCount."!
>
> registerService
> | st rc ste  |
> st := StructureArray length:2 elementClass: SERVICE_TABLE_ENTRY.
> ste := SERVICE_TABLE_ENTRY new.
> ste lpServiceName: ('' asParameter ).
>
> serviceMainCallback := ExternalCallback receiver: self selector:
> #serviceMain:argv: descriptor: ##(ExternalDescriptor argumentTypes: 'dword
> lpvoid') .
> ste lpServiceProc: (serviceMainCallback asParameter yourAddress ).
> st at:1 put: ste .
> SessionManager current trace: 'Calling startServiceCtrlDispatcher'.
> rc := AdvApiLibrary default startServiceCtrlDispatcher: st.
> SessionManager current trace: 'Return code:',(rc displayString).
> SessionManager current trace: 'Error:',((KernelLibrary default
> getLastError ) displayString).!
>
> serviceHandler: controlCode
> | status rc |
> SessionManager current trace: 'serviceHandler Callback' , controlCode
> displayString.
> controlCode = 1
> ifTrue:
> [status := SERVICE_STATUS new.
> status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
> status dwCurrentState: 3. "SERVICE_STOP_PENDING"
> status dwControlsAccepted: 7. "SERVICE_ACCEPT_PAUSE_CONTINUE |
> SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP"
> status dwWin32ExitCode: 0.
> status dwServiceSpecificExitCode: 0.
> status dwCheckPoint: 0.
> status dwWaitHint: 0.
> rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
> lpHandlerProc: status.
> SessionManager current trace: 'serviceHandler called setServiceStatus:' ,
> rc displayString.
> rc
> ifFalse:
> [SessionManager current
> trace: 'setServiceStatus error:' , KernelLibrary default getLastError
> displayString].
> self stopService] ifFalse: [ (controlCode  = 2) ifTrue: [status :=
> SERVICE_STATUS new.
> status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
> status dwCurrentState: 3. "SERVICE_STOP_PENDING"
> status dwControlsAccepted: 7. "SERVICE_ACCEPT_PAUSE_CONTINUE |
> SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP"
> status dwWin32ExitCode: 0.
> status dwServiceSpecificExitCode: 0.
> status dwCheckPoint: 0.
> status dwWaitHint: 0.
> rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
> lpHandlerProc: status.
> SessionManager current trace: 'serviceHandler called setServiceStatus:' ,
> rc displayString.
> rc
> ifFalse:
> [SessionManager current
> trace: 'setServiceStatus error:' , KernelLibrary default getLastError
> displayString]] ].
> ^nil!
>
> serviceMain: argc argv: ptrArgv
> | rc status |
> SessionManager current trace: 'serviceMain Callback'.
> "Register handler"
> serviceHandlerCallback := ExternalCallback
> receiver: self
> selector: #serviceHandler:
> descriptor: ##(ExternalDescriptor argumentTypes: 'dword').
> serviceStopMutex := Semaphore new.
> serviceStatusHandle := AdvApiLibrary default registerServiceCtrlHandler:
> '' asParameter
> lpHandlerProc: serviceHandlerCallback asParameter yourAddress.
> SessionManager current trace: 'serviceStatus handle' , serviceStatusHandle
> displayString.
> status := SERVICE_STATUS new.
> status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
> status dwCurrentState: 4. "SERVICE_RUNNING"
> status dwControlsAccepted: 5. "SERVICE_ACCEPT_SHUTDOWN |
> SERVICE_ACCEPT_STOP"
> status dwWin32ExitCode: 0.
> status dwServiceSpecificExitCode: 0.
> status dwCheckPoint: 0.
> status dwWaitHint: 0.
> rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
> lpHandlerProc: (status  ).
> SessionManager current trace: 'serviceMain called setServiceStatus:' , rc
> displayString.
> rc
> ifFalse:
> [SessionManager current
> trace: 'setServiceStatus error:' , KernelLibrary default getLastError
> displayString].
> "block until service stopped"
> "serviceStopMutex wait."
> ^0!
>
> stopService
> | status rc |
> status := SERVICE_STATUS new.
> status dwServiceType: 16. "SERVICE_WIN32_OWN_PROCESS"
> status dwCurrentState: 1. "SERVICE_STOPPED"
> status dwControlsAccepted: 7. "SERVICE_ACCEPT_PAUSE_CONTINUE |
> SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP"
> status dwWin32ExitCode: 0.
> status dwServiceSpecificExitCode: 0.
> status dwCheckPoint: 0.
> status dwWaitHint: 0.
> rc := AdvApiLibrary default setServiceStatus: serviceStatusHandle
> lpHandlerProc: status.
> SessionManager current trace: 'serviceHandler called setServiceStatus:' ,
> rc displayString.
> rc
> ifFalse:
> [SessionManager current
> trace: 'setServiceStatus error:' , KernelLibrary default getLastError
> displayString].
> self exit.!
>
> tick
> SessionManager current trace: 'tick'.
> tickCountMutex critical: [tickCount := tickCount + 1]! !
> !NTServiceSessionManager categoriesFor: #keepAlive!public! !
> !NTServiceSessionManager categoriesFor: #main!public! !
> !NTServiceSessionManager categoriesFor: #registerService!public! !
> !NTServiceSessionManager categoriesFor: #serviceHandler:!public! !
> !NTServiceSessionManager categoriesFor: #serviceMain:argv:!public! !
> !NTServiceSessionManager categoriesFor: #stopService!public! !
> !NTServiceSessionManager categoriesFor: #tick!public! !
>
> "Binary Globals"!


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

John Rubier
Martin Rubi wrote:

> Interesting !
>
> A question: would you, John, and Bill mind if a take code and ideas from
> both of your packages and merge them with some of my stuff and try to make a
> fully featured service framework with all of it ? Or, if you prefer, I can
> send my stuff to you.
>
> best regards
> martin
>

Martin,

Feel free to use the code. I'm glad I was able to provide something
useful instead of mooching for once! :)

Take care,

John


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Schwab,Wilhelm K
John Rubier wrote:
> Martin Rubi wrote:
>> Interesting !
>>
>> A question: would you, John, and Bill mind if a take code and ideas from
>> both of your packages and merge them with some of my stuff and try to make a
>> fully featured service framework with all of it ? Or, if you prefer, I can
>> send my stuff to you.

Go for it!

Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Running as a service

Martin Rubi
Ok, I've done my first attemp to put the whole thing together.
I've tested it with a trivial (and I mean trivial) service in win2k and XP,
and it _seems_ to work. However, I would not say it does until I run a real
service with it.

The package can be found here:
http://mrubi.users.dolphinmap.net/Martin Rubi goodies.zip

under de "nt service" folder.

Thanks again to Bill and John for letting me borrow their work.
Please send any comment (since I may use this for real, specially the
comments saying why it would be a bad idea to do so) you may have.

best regards
martin