I've got it in use in a TDataModule's OnCreate handler. The only line in the
handler is this:
Timer1.Enabled := True;
The timer never seems to fire. Why?
To test it, I also did it with a normal Win32 app and in the timer event I
set it to update a forms caption. It worked perfectly updating the form so
why won't it work as a service?
The very first line of the timer event is:
Utils.Log('log.txt', 'entering timer loop');
This line is never called, and I never find it in the log file.
What is going on? - This stupidly simple little program has been holding me
up for the last 4 hours and now it's really annoying me.
Please help.
JD
> I've got it in use in a TDataModule's OnCreate handler. The only line
> in the handler is this: Timer1.Enabled := True;
>
> The timer never seems to fire. Why?
>
> To test it, I also did it with a normal Win32 app and in the timer
> event I set it to update a forms caption. It worked perfectly
> updating the form so why won't it work as a service?
AFAIR the timer needs a HWND, in order to work, or a callback procedure.
No problem in an application, but in a service?
DoDi
TTimer creates its own window. But there also needs to be a message pump.
--
Rob
I'm using uCiaService unit to make a normal app with a TDataModule work as a
service. I suppose I could instead use a normal Service Application but this
takes away my ability to have a .exe which can run standalone...
How can I create something to trigger my event outside of OnCreate?
Obviously I could call the main loop from OnCreate but then OnCreate would
never finish and I'm not really happy with this method.
What can I do?
Oh, one other thing, why is TTimer available on the component pallette if it
shouldn't be used in a TDataModule? :confused: (All other visual components
vanish)
JD
"Rob Kennedy" <m...@privacy.net> wrote in message
news:4802bb4f$1...@newsgroups.borland.com...
Same thing - Works fine as a win32 app, as soon as it runs as a service
NOTHING!!!
This is so frustrating - Can someone help please?
"Jamie Dale" <jamie...@yahoo.com> wrote in message
news:48030113$1...@newsgroups.borland.com...
kind regards
Mike
I don't know. I've never written a service using Delphi's framework or
any other. My services have been written using only the API. What I
would do in your case is call CreateWaitableTimer and then incorporate
its handle into my existing calls to MsgWaitForMultipleObjects, where my
service waits for other things to trigger it to do work.
Someone else here can probably tell you whether there's already
something in a Delphi service where you can wait for handles to become
signaled. Rabatscher suggested using an event, so he probably knows.
> Oh, one other thing, why is TTimer available on the component pallette if it
> shouldn't be used in a TDataModule? :confused: (All other visual components
> vanish)
Who said it shouldn't be used in a data module? It descends from
TComponent but does not descend from TControl, so you can put it on a
data module just like you can any other component that fits that pattern.
--
Rob
> The timer never seems to fire. Why?
What thread context are you creating the TDataModule in? TTimer is a
message-based timer. TTimer's constructor creates a hidden window to
receive WM_TIMER messages. As such, it needs an active message queue in
order to run correctly. If the OnTimer event is not firing, then you are
likely creating the TTimer instance in the context of a thread that does not
have a running message queue.
Gambit
> AFAIR the timer needs a HWND, in order to work
TTimer creates its own HWND internally.
> or a callback procedure.
You are referring to the SetTimer() API function, not the TTimer component.
Either way, however, requires a running message queue in order to receive
the WM_TIMER message that allows the OS to pass the message to the HWND, or
to trigger the specified callback.
> No problem in an application, but in a service?
TTimer can work in a service, but it requires extra work on your part to
make sure it is being used in the correct place within the service.
Gambit
> I'm using uCiaService unit to make a normal app with a TDataModule
> work as a service. I suppose I could instead use a normal Service
> Application but this takes away my ability to have a .exe which can
> run standalone...
You can use svCom for that.
> What can I do?
You must create the TDataModule (and thus TTimer) in the context of a thread
that has a running message queue.
> Oh, one other thing, why is TTimer available on the component
> pallette if it shouldn't be used in a TDataModule?
Because this problem has nothing to do with TDataModule. It has to do with
threading.
> All other visual components vanish
TDataModule is not a TWinControl descendant, so it cannot host visual
components. TTimer is not a TControl descendant, and thus is not a visual
component.
Gambit
> What can I do?
Write a proper service instead of trying to shoehorn a UI app into a
service straightjacket. The services OnStart event (if you use the
standard service template that comes with Delphi) should create a
worker thread, the thread should create an instance of the datamodule
at the start of its Execute method and then enter a simple message
loop. This would allow the timer on the DM to operate. The services
OnStop event (as well as OnPause and OnContinue, if you intend to
support those) would send custom messages to the worker thread via
PostThreadMessage. The thread can process those in its message loop to
stop and restart the DMs timer and to terminate itself, if required. Do
*NOT* enable or disable the DM timer from any other thread!
> Oh, one other thing, why is TTimer available on the component
> pallette if it shouldn't be used in a TDataModule? :confused: (All
> other visual components vanish)
Oh, it is perfectly usable on a DM since it create its own internal
window. All it needs to operate is a message loop in the thread that
creates the DM instance.
--
Peter Below (TeamB)
Don't be a vampire (http://slash7.com/pages/vampires),
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
> What thread context are you creating the TDataModule in?
No idea. Initially the project (.dpr) source used uCiaService.CreateForm()
to create the TDataModule when installed as a service. The TTimer was on the
TDataModule.
Since then I've created a worker thread which is supposed to be created in
TDataModule.OnCreate. Thing is the thread only seems to work as a win32.exe
not a service.exe. It is so frustrating!!!
> TTimer is a message-based timer. TTimer's constructor creates a hidden
> window to receive WM_TIMER messages. As such, it needs an active message
> queue in order to run correctly.
Are we talking about Application.ProcessMessages ?
> If the OnTimer event is not firing, then you are likely creating the
> TTimer instance in the context of a thread that does not have a running
> message queue.
How do I find out?
"Rabatscher Michael" <m.raba...@tom-medical.com> wrote in message
news:48031e2c$1...@newsgroups.borland.com...
> Write a proper service instead of trying to shoehorn a UI app into a
> service straightjacket.
But you can't run a normal service.exe as a win32.exe - I'd like to have
both in one .exe
uCiaService has worked well for me with other simple projects in the past...
> Oh, it is perfectly usable on a DM since it create its own internal
> window. All it needs to operate is a message loop in the thread that
> creates the DM instance.
Are we talking about using Application.ProcessMessages ?
> You must create the TDataModule (and thus TTimer) in the context of a
> thread that has a running message queue.
Forgive me if I'm being thick here but... huh?
Whats all this message queue stuff about then and how do I know if I have
one or not?
Basically in my project source, uCiaService creates the TDataModule and then
uCiaService.Run
I've no idea about the message Queue - If you could explain I'd be grateful!
JD
> But you can't run a normal service.exe as a win32.exe -
> I'd like to have both in one .exe
Service .exes and non-service .exes use very different architectures. You
can't really have them in the same .exe file without using a framework
specifically designed for that. Even then, putting a UI into a service
would no longer work in Vista onwards anyway, so it makes more sense to keep
them separated.
> Are we talking about using Application.ProcessMessages ?
ProcessMessages() is the VCL's function for checking for pending messages at
that moment (in the main thread only - it is not very safe to use in worker
thread). It is not the actual message loop itself. That is inside of
Application.Run() instead.
Gambit
> Whats all this message queue stuff about then
You do know what a message queue is in general, right? You can't do Win32
UI development without it.
> how do I know if I have one or not?
The main thread in any VCL app has a message queue. So does the worker
thread that TService creates internally to handle SCM requests. Any other
thread does not have a message queue unless its code creates one manually.
> Basically in my project source, uCiaService creates the TDataModule
> and then uCiaService.Run
I have no idea what uCiaService is or what it does internally.
Gambit
> No idea.
Then I suggest you read up on how multi-threaded programming works in
general, and how the Win32 API uses message loops.
> Initially the project (.dpr) source used uCiaService.CreateForm()
> to create the TDataModule when installed as a service.
> The TTimer was on the TDataModule.
Then the TDataModule, and thus its TTimer, is being created in the context
of the main thread. All WM_TIMER messages for the TTimer will be received
by the main thread's message loop, if it has one. I have no clue how
uCiaService is implemented, so I have no clue if it even starts a message
loop. Since your TTimer is not working, then it stands to reason that
uCiaService is not running a message loop.
Gambit
procedure TService1.ServiceCreate(Sender: TObject);
begin
fSignal := TSimpleEvent.Create;
end;
procedure TService1.ServiceDestroy(Sender: TObject);
begin
FreeAndNil(fSignal);
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
while fSignal.WaitFor(500) = wrTimeout do
begin
// execute code here
end;
end;
procedure TService1.ServiceShutdown(Sender: TService);
begin
fSignal.SetEvent;
end;
> procedure TService1.ServiceCreate(Sender: TObject);
> begin
> fSignal := TSimpleEvent.Create;
> end;
>
> procedure TService1.ServiceDestroy(Sender: TObject);
> begin
> FreeAndNil(fSignal);
> end;
Rather than using the OnCreate/Destroy events, I would suggest using the
OnStart/Stop/Shutdown events instead. That way, the TSimpleEvent is
instantiated only when the service is actually running. OnCreate/Destroy
are always called, whether the service is being installed/uninstalled or is
running.
Gambit
No, that function may do other things in addition to what you need and
it also does not wait for messages to come in, it returns immediately
when there is no message to process. For a thread that is controlled by
messages that is not optimal. A secndary threads message loop would be
something like
var
Msg: TMsg;
res: Integer;
begin
While not terminated Do Begin
res := Integer( GetMessage(Msg, 0, 0, 0 ));
If res = -1 Then
terminate // error
else if res = 0 then
terminate // WM_QUIT received
else
DispatchMessage( Msg );
End; { While }
You would either put this into the threads Execute method or in a
method you call from Execute.
The declaration of GetMessage is typcially MS schizophrenic: they
declare it as BOOL but in fact require you to handle *three* return
conditions. Go figure... Note that the thread will be suspended in
GetMessage until it actually gets a message. To close it down you need
to send it a WM_QUIT message (via PostThreadMessage, for instance), or
call its Terminate method *and* send it any other message (even a
WM_NULL will do). Use the Thread.Handle as first parameter to
PostThreadMessage.
> While not terminated Do Begin
> res := Integer( GetMessage(Msg, 0, 0, 0 ));
Since GetMessage() waits for a message to arrive, I would suggest using
PeekMessage() or MsgWaitForMultipleObjects() instead.
Gambit
Hi Remy, below is the entire source unit for uCiaService - Can you advise if
it starts a message queue?:
unit uCiaServiceTools;
interface
uses
SysUtils, Classes, Windows, SvcMgr, WinSvc;
type
TCiaService = class(TService)
protected
procedure Start(Sender: TService; var Started: boolean);
procedure Stop(Sender: TService; var Stopped: boolean);
procedure Execute(Sender: TService);
public
function GetServiceController: TServiceController; override;
constructor CreateNew(AOwner: TComponent; Dummy: integer = 0); override;
procedure CreateForm(InstanceClass: TComponentClass; var Reference);
procedure Run;
end;
function CiaStartService(DisplayName: string): Boolean;
function CiaIsService: boolean;
var
CiaService: TCiaService;
implementation
var
FIsService: boolean;
FServiceName: string;
FDisplayName: string;
//------------------------------------------------------------------------------
//----
TCiaService -------------------------------------------------------------
//------------------------------------------------------------------------------
procedure ServiceController(CtrlCode: dword); stdcall;
begin
CiaService.Controller(CtrlCode);
end;
//------------------------------------------------------------------------------
function TCiaService.GetServiceController: TServiceController;
begin
result := ServiceController;
end;
//------------------------------------------------------------------------------
procedure TCiaService.CreateForm(InstanceClass: TComponentClass; var
Reference);
begin
SvcMgr.Application.CreateForm(InstanceClass, Reference);
end;
//------------------------------------------------------------------------------
procedure TCiaService.Run;
begin
SvcMgr.Application.Run;
end;
//------------------------------------------------------------------------------
constructor TCiaService.CreateNew(AOwner: TComponent; Dummy: integer);
begin
inherited;
AllowPause := False;
Interactive := True;
DisplayName := FDisplayName;
Name := FServiceName;
OnStart := Start;
OnStop := Stop;
end;
//------------------------------------------------------------------------------
procedure TCiaService.Start(Sender: TService; var Started: boolean);
begin
Started := True;
end;
//------------------------------------------------------------------------------
procedure TCiaService.Execute(Sender: TService);
begin
while not Terminated do
ServiceThread.ProcessRequests(True);
end;
//------------------------------------------------------------------------------
procedure TCiaService.Stop(Sender: TService; var Stopped: boolean);
begin
Stopped := True;
end;
//------------------------------------------------------------------------------
//----
Various -----------------------------------------------------------------
//------------------------------------------------------------------------------
function CiaIsService: boolean;
begin
Result := FIsService;
end;
//------------------------------------------------------------------------------
function CiaStartService(DisplayName: string): Boolean;
var
Mgr, Svc: Integer;
UserName, ServiceStartName: string;
Config: Pointer;
Size: DWord;
n: integer;
begin
FDisplayName := DisplayName;
FServiceName := DisplayName;
for n := 1 to Length(FServiceName) do
if FServiceName[n] = ' ' then
FServiceName[n] := '_';
FIsService := FindCmdLineSwitch('install', ['-','\','/'], True) or
FindCmdLineSwitch('uninstall', ['-','\','/'], True);
if FIsService then begin
SvcMgr.Application.Initialize;
CiaService := TCiaService.CreateNew(SvcMgr.Application, 0);
Result := True;
Exit;
end;
Mgr := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if Mgr <> 0 then begin
Svc := OpenService(Mgr, PChar(FServiceName), SERVICE_ALL_ACCESS);
FIsService := Svc <> 0;
if FIsService then begin
QueryServiceConfig (Svc, nil, 0, Size);
Config := AllocMem(Size);
try
QueryServiceConfig(Svc, Config, Size, Size);
ServiceStartName := PQueryServiceConfig(Config)^.lpServiceStartName;
if CompareText(ServiceStartName, 'LocalSystem') = 0 then
ServiceStartName := 'SYSTEM';
finally
Dispose(Config);
end;
CloseServiceHandle(Svc);
end;
CloseServiceHandle(Mgr);
end;
if FIsService then begin
Size := 256;
SetLength(UserName, Size);
GetUserName(PChar(UserName), Size);
SetLength(UserName, StrLen(PChar(UserName)));
FIsService := CompareText(UserName, ServiceStartName) = 0;
end;
Result := FIsService;
if FIsService then begin
SvcMgr.Application.Initialize;
CiaService := TCiaService.CreateNew(SvcMgr.Application, 0);
end;
end;
end.
uCiaService uses this in uCiaService.Run:
procedure TCiaService.Run;
begin
SvcMgr.Application.Run;
end;
Does that help?
Yeah it handles windows messages - EG mousedown and onclose etc right? For
some reason when I first queried it I didn't put 2 & 2 together (i was a bit
slow last night)
>> how do I know if I have one or not?
>
> The main thread in any VCL app has a message queue. So does the worker
> thread that TService creates internally to handle SCM requests. Any other
> thread does not have a message queue unless its code creates one manually.
Problem is that when I found TTimer wouldn't work, I copied the code into a
seperate thread which did not use a TTimer but a repeat/until loop instead.
In this loop, idIcmpClient is set to ping every ip in a certain range on my
network to check if it is online - if not it shuts down the system it is
running on. Problem is the loop doesn't seem to run - without the TTimer!
It really is frustrating...
Yeah probably right! I got the Delphi 6 Dev Guide but the sheer size of it
is rather off putting... Add to that my total lack of spare time these days
andit isn't really surprising I'm not learning much :(
>
>> Initially the project (.dpr) source used uCiaService.CreateForm()
>> to create the TDataModule when installed as a service.
>> The TTimer was on the TDataModule.
>
> Then the TDataModule, and thus its TTimer, is being created in the context
> of the main thread.
So theoretically IF uCiaService was doing things correctly a message queue
would be in place... Thats IF though..
> All WM_TIMER messages for the TTimer will be received by the main
> thread's message loop, if it has one.
So it probably doesn't... Damn..
> I have no clue how uCiaService is implemented, so I have no clue if it
> even starts a message loop.
I posted the source in a seperate reply - If you could spare a few mins to
take a look I'd be very grateful.
JD
> Yeah it handles windows messages - EG mousedown and onclose etc right?
Yes, amongst many others.
> Problem is that when I found TTimer wouldn't work, I copied
> the code into a seperate thread which did not use a TTimer but
> a repeat/until loop instead. In this loop, idIcmpClient is set to
> ping every ip in a certain range on my network to check if it is
> online - if not it shuts down the system it is running on. Problem
> is the loop doesn't seem to run - without the TTimer!
Then your code is probably deadlocking itself.
Gambit
> Hi Remy, below is the entire source unit for uCiaService -
> Can you advise if it starts a message queue?:
Yes, it does, for the thread that calls Run().
Gambit
So what could I be doing wrong? - The code runs fine as Win32 but not as a
service!
If it has a message queue then it should be fine right?
I'm definately having a bad week...
> So what could I be doing wrong? - The code runs fine as Win32 but not as a
> service!
Since the TTimer is being created in the context of the main thread, and
ciaService is running a message loop in the context of the main thread, the
only way the TTimer::OnTimer event would not work is if either the
TTimer::Enabled property is false, or something else in your project is
swallowing WM_TIMER messages before TTimer can receive them.
Gambit
It is intended to wait, for a message-driven thread that is a proper
design, IMO. Why implement a separate wait mechanism if Windows
supplies one?
> It is intended to wait, for a message-driven thread that is
> a proper design, IMO. Why implement a separate wait
> mechanism if Windows supplies one?
If you were waiting on only a message, that would be fine. But you are
having to post a message to wake up the thread in order to handle the
Terminated property in a timely manner as well. So the thread could just
timeout its waiting periodically to check that without having to post
anything.
Gambit
Timer.Enabled := True; - I coded the line in specifically to make sure that
i wasn't relying on the default enabled property.
So, I guess that leaves only the 2nd suggestion - something swallowing the
WM_TIMER messages. What could this be? - Any ideas?