Solved

Use a Datamodule in a Service Application

Posted on 2006-07-10
12
1,825 Views
Last Modified: 2012-05-05
In an effort to be efficient, I wrote a prototype for a Service Application (as created by the Delphi Service Application Wizard) by creating a Datamodule with ALL the functionality needed to run the TCP sockets and ADO Database components that I needed.  I did this so I could hook it into a Regular Win32 Application to test and debug.  After successfully testing the Datamodule in the Win32 application environment I am trying to hook the Datamodule into a Service Application template that I created, I can get nowhere in getting it to run.  I'm not much on debugging services although I did get Delphi to connect to the service as it tried to start (using the register entries suggested by Delphi), but only with register and data info which I'm not proficient enough to understand.  Could someone indicate whether this strategy of using a Datamodule with Component functionality encapsulating the primary service functions is workable and give a simple template example of how to hook it up if it is??
0
Comment
Question by:namuh1
  • 5
  • 5
  • 2
12 Comments
 
LVL 28

Expert Comment

by:ciuly
ID: 17075447
tell you the truth, I never used a datamodule and I'm writing delphi applications for more than 6 years.

so, I am not exactly the right person to tell you if datamodul is a good thing for your project or not, but I could tell you how I would do this:

create a class that encapsulates the sockets/db components/etc, (since you will be using this in a service, you will usually have no user interface, though this is possible)
next step is to create the application logic in that class: create the components, relate each of them, destroy them, etc
next step, create the service, import the class file (uses unitname) create an instance of the class, initialize whatever needs to be initialized: make sure service is configured correctly for your needs (user/pass, other service dependent properties, necessary threads, etc (you WILL need threads since using sockets: either in the service, or in the class that holds the socket/db/etc)

this "model" never failed me ... yet :)
0
 
LVL 28

Expert Comment

by:ciuly
ID: 17075472
btw, that "a class" can be more classes, related to each other. it can be a component or more components. it's just somethgin that encapsulates the classes copmponents used in the service logic partially following the MVC (Model-View-Controller) desing patter.
0
 

Author Comment

by:namuh1
ID: 17075640
ciuly,
Your description is quite similar to what I 'thought' I was attempting.  The only difference is that my 'class' is simply a descendent of TDataModule.  I chose this because TDataModule in the Designer gives a non-visual form to embed components that I could verify as working in when used in a normal VCL Application.  I was attempting to use regular Delphi socket components, which I have used successfully in the Form based normal app environment.  My assumption was that the TDataModule and the non-visual form would handle the instantiation and destruction of the socket components in the same way that it does in a visual app.  The socket components have thread management built into them so again I assumed that would work as well. So I am not sure why this approach is failing since it is close to yours.  The place where I am unsure is in how the code on the DataModule or in your class, for that matter, gets executed.  I initialize the DataModule properties in the OnStart event of the TService and did try per template example to put a ServiceThread.ProcessRequests(True) in a loop in the .ServiceExecute procedure.  But it still does not work.  The error coming back is that the service starts then stops with no error reporting that I can glean info from i.e. no error numbers in the failure message and nothing in the logged application Events.  I thought further that there might be a problem with the components creating and managing their own threads, but that would be true in your strategy as well.... so... still so ever lost.
0
 

Author Comment

by:namuh1
ID: 17075724
Sorry, I was answering your first comment while you posted the second.  I am unfamiliar with the term MVC or the terms desing patter??  I realize from our exchange that the part where my understanding breaks down is the 'hidden' items come into play.  TService seems to automatically have ServiceThread built into it and I don't see where the component code in the imported class you discussed (either your class or my class(DataModule) ever has its Message/Event structure handled???  I'm simply guessing that the invisible wrapper code calls TService.ServiceExecute and 'somehow' the ServiceThread.ProcessRequests somehow enables the code in the thread to run BUT WHOSE THREAD since there seems to be no connection to the imported class.  Also my familiarity with Threads requires placeing code in the TThread.Execute method which I have yet to find an instance of in the TService model.  So in short how does yours or my class code ever get executed?
0
 
LVL 28

Assisted Solution

by:ciuly
ciuly earned 250 total points
ID: 17075758
that means you migth have some exceptions. in such cases, it is a good idea to check the event log ;)

this is what I didn't mention, sorry:

1) I use a basic logging system sto track execution, errors, etc (puttin a "now entering function x" "now leaving function x" etc stuff until I get it working ok)

procedure expandLogFilePath;
begin
  if logFile[2]<>':' then
    logFile:=getInstallDir+'logs\'+logFile;
end;

procedure log(s:string);
var f:textfile;
begin
  try                
    if logFile[2]<>':' then
      expandLogFilePath;
    assignfile(f,logFile);
    try
      append(f);
    except
      rewrite(f);
    end;
    writeln(f,'['+datetostr(date)+' '+timetostr(time)+'] : '+s);
    closefile(f);
  except
    on e:exception do
    begin
      assignfile(f,GetEnvironmentVariable('temp')+'\ServiceCriticalLog.log');
      try
        append(f);
      except
        rewrite(f);
      end;
      writeln(f,'['+datetostr(date)+' '+timetostr(time)+'] : critical error: <'+e.message+'> while logging message: <'+s+'> in file: ['+logFile+']');
      closefile(f);// if this part also fails, only the system event log will help you track doen the issue, but usually it's a "rights' issue, or file in use, or stuff like this
    end;
  end;
end;

procedure doError(msg:string; e:exception); overload;
begin
  exitCode:=2;
  log(msg+e.message);
  if raiseExceptions then
    raise e;
end;

procedure doError(e:exception); overload;
begin
  doError('An error has occured: ',e);
end;

2) my "working" is usually like this:

procedure TServiceMonitor.ServiceExecute(Sender: TService);// this is classic. never changed a bit :)
begin
  log('Execution started...');
  try
    while not Terminated do
      ServiceThread.ProcessRequests(True);// wait for termination
  except
    on e:exception do
      log('error occured: '+e.message);
  end;
  log('Execution finished.');
end;

procedure TServiceMonitor.ServiceCreate(Sender: TObject);// basically, it's like this
begin
  msg('creating');
  try
    // create and initialize stuff here
  except
    on e:exception do
      log('error occured while creating: '+e.message);
  end;
  msg('done creating');
end;

procedure TServiceMonitor.ServiceDestroy(Sender: TObject);
begin
  msg('destroying');
  try
    try
      // start destroying stuff
    except
      on e:exception do
        msg('exception while terminating service: '+e.message);
    end;
    msg('wait for stuff to get destroyrd');
    while not finished do (this can be: communication, file/db operations, etc)
      sleep(100);// wait for stuff to finish
    msg('done');
    // free rest of the stuff (db/tcp/etc)
    msg('exitcode: '+inttostr(exitcode));// I usually have such thing :) helps control a few processes
    if exitcode>0 then// some error occured during destruction. we need to close so force it
    begin
      msg('halting...');
      halt(exitcode);
    end;
  except
    on e:exception do
      log('error while destroying: '+e.message);
  end;
end;

that is the general idea of my service programming.
0
 
LVL 28

Expert Comment

by:ciuly
ID: 17075824
well ... its like this: since it is a service, it does not require a separate message thread.

basically what happens is something like this:

you have a socket listening.
the event are setup: accept, etc

when a client connects, the event is fired, the eventhandler of the service kicks in and handles the event. remember that the service is anotehr application it has an event handler just like the regular application. it's nothing different there.

but the difference between a service and a regular applicaiton is that the service reacts mainly to "non-user" interaction: like a windows message, a socket connection, a pipe event, etc. that is when the code starts to roll.
you will need to design your service in such a thing that this happens.

BUT. services are meant for a certain type of applications: the ones that need to react only on non-user events. so if you application requires user-input, then take it out from the service into a separate application and have that communicate with the service. this is best practice. you can do this from the service as well, but I personally prefer not to mes the 2 application types
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 

Author Comment

by:namuh1
ID: 17076105
Yes, this is a 'total' non user interaction program.  It simply sits and listens on a TCP port for log data from a Mitel Telephone system.  I must still be missing some simple thing.  If you take the same DataModule and put it into a VCL Application wrapper, it works perfectly.  The only thing I can think of that may be a problem is that the OnTriggerDataAvailable event of the Socket component that listens for and receives data has its vector replaced in the VCL wrapper code.  I.E. it is not handled in the DataModule Class.  I simply assign ....     _MitelDataModule.OnStringAvail := PLogStringAvail; in the .ServiceExecute method before entering the ServiceThread.ProcessRequest loop.  OnStringAvail is the event handler defined in the DataModule and PLogStringAvail is the override code in the VCL Wrapper.  Your post of 'logging/debuggin' code may still help me to find where the service is dying pre-maturely, but it will take me a little while to build it in.... Smiling, but pulling hair out .  Many thanks for continuing to help
0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 250 total points
ID: 17077301
I have several services that use a datamodule as you describe.
It is set up so that I can use a compiler directive if I wish to run it as an app for debugging
e.g alt-project-options-Directories/Conditionals. Conditionals  
APP


my .dpr looks like this

program FBSyncService;

uses
{$IFDEF APP}
  Forms, // if we are compiling this to run as an app, use forms so we can show our user debug form
{$ELSE}
  SvcMgr, // otherwise just as a normal service
{$ENDIF}
  FBSync_Manual in 'FBSync_Manual.pas' {fFBSync_Manual},
  FBSync_Service in 'FBSync_Service.pas' {FBSync: TService},
  FBSync_Worker in 'FBSync_Worker.pas' {dmFBSync: TDataModule};

{$R *.RES}

begin
  Application.Initialize;
  Application.Title := 'FB Sync';
{$IFDEF APP} // only create the manual form if we are running as an app
  Application.CreateForm(TfFBSync_Manual, fFBSync_Manual);
{$ELSE} // otherwise just a normal service
  Application.CreateForm(TFBSync, FBSync);
{$ENDIF}
  Application.Run;
end.

*** The FBSync_Manual looks like this

type
  TfFBSync_Manual = class(TForm)
...
    procedure ReceiveLogMessage(Sender: TObject; const s: string; const LogMessageType: integer; const ImportanceLevel: integer);
pubic
...
end;

procedure TfFBSync_Manual.bbStartClick(Sender: TObject);
    begin
        if not assigned(dmFBSync) then
        begin
            dmFBSync := TdmFBSync.Create(self);
            dmFBSync.OnLogMessage := fFBSync_Manual.ReceiveLogMessage; // this is so we can easily get debug messages from the worker datamodule
            dmFBSync.StartWork;
        end;
        InputEventLog.LogInputEvent( evt_Information, 1, '', 'Started');
        lInfo.Caption := 'Started';
    end;

procedure TfFBSync_Manual.bbStopClick(Sender: TObject);
    begin
        FreeAndNil(dmFBSync);
        InputEventLog.LogInputEvent( evt_Information, 2, '', 'Stopped');
        lInfo.Caption := 'Stopped';
    end;

procedure TfFBSync_Manual.ReceiveLogMessage(Sender: TObject; const s: string;
  const LogMessageType, ImportanceLevel: integer);
    var
        s2: string;
    begin
        if Closing then exit;
        case ImportanceLevel of
            il_Boring: s2 := '. ' + s;
            il_Average: s2 := '  ' + s;
            il_Important: s2 := '! ' + s;
            else s2 := '? ' + s;
        end;

        case LogMessageType of
            lt_Information: s2 := 'I  ' + s2;
            lt_Warning: s2 := 'W  ' + s2;
            lt_Error: s2 := 'E  ' + s2;
            else s2 := '? ' + s2;
        end;


        Memo1.Lines.Add(S);
        while (Memo1.Lines.Count > 100) do
          Memo1.Lines.delete(0);
    end;


*** The main service unit looks like this

unit FBSync_Service;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
  NTCommon, (*EventLog, MyEventLogComponent, *)
  FBSync_Worker; // <-- the datamodule that does the work

type
  TFBSync = class(TService)
//    MyEventLog: TMyEventLog;
    procedure ServiceStart(Sender: TService; var Started: Boolean);
    procedure ServiceStop(Sender: TService; var Stopped: Boolean);
    procedure ServiceExecute(Sender: TService);
  private
    { Private declarations }
  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;

var
  FBSync: TFBSync;

implementation

{$R *.DFM}

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  FBSync.Controller(CtrlCode);
end;

function TFBSync.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;

procedure TFBSync.ServiceStart(Sender: TService; var Started: Boolean);
    begin
        if not assigned(dmFBSync) then
        begin
            dmFBSync := TdmFBSync.Create(self);
            dmFBSync.StartWork;
        end;
//        MyEventLog.LogEvent( evt_Information, 1, '', 'Started');
        Started := true;
    end;

procedure TFBSync.ServiceStop(Sender: TService; var Stopped: Boolean);
    begin
        FreeAndNil(dmFBSync);
//        MyEventLog.LogEvent( evt_Information, 2, '', 'Stopped');
        Stopped := true;
    end;

procedure TFBSync.ServiceExecute(Sender: TService);
    begin
        while not terminated do
        begin
            ServiceThread.processRequests( true );
        end;
    end;

end.


*** and the datamodule has these bits in it

const
      lt_Information = 0;
      lt_Warning     = 1;
      lt_Error       = 2;
      il_Important  = 0;
      il_Average    = 1;
      il_Boring     = 2;

type TOnLogMessage = procedure(Sender: TObject; const s: string; const LogMessageType: integer; const ImportanceLevel: integer) of object;


  private
    fOnLogMessage: TOnLogMessage;
    procedure AddLoggingMessage(s: string; LoggingType: integer; ImportanceLevel: integer); // 0 = boring, 1 = average, 2 = important as above
  public
    property OnLogMessage: TOnLogMessage read fOnLogMessage write fOnLogMessage;
  end;

procedure TdmFBSync.AddLoggingMessage(s: string; LoggingType, ImportanceLevel: integer);
  begin
        if assigned(fOnLogMessage) then
          fOnLogMessage(self, s, LoggingType, ImportanceLevel)
//else log to event log or file
  end;
procedure TdmFBSync.StartWork;
    begin
        AddLoggingMessage('Starting', lt_Information, il_Average);
// turn on the TCP server etc...
    end;
 
procedure TdmFBSync.StopWork;
    begin
        AddLoggingMessage('Stopping', lt_Information, il_Average);
// turn off the TCP server etc...
        AddLoggingMessage('Stopped', lt_Information, il_Average);
    end;
0
 

Author Comment

by:namuh1
ID: 17083068
Well, between the two of you, I have been lead to the solution.  ciuly provided the logging framework for me to find my dumb mistake after the fact. And TheRealLoki gave me a framework that proves the 'datamodule' concept works and even, in fact, how to combine a debugging version and runtime version into the same project.  What is the correct thing to do here?  My intuition says split the points 50/50?  Would this be satisfactory?
0
 
LVL 28

Expert Comment

by:ciuly
ID: 17083304
it's fine with me
0
 

Author Comment

by:namuh1
ID: 17083314
TheRealLoki, Just one question for completeness: in implementing your template, I find no call to StopWork.  May I assume such call should reside in the ServiceStop event handler?

And again a very big thanks to both of you ciuly and TheRealLoki.  I can see that a lot of effort went into helping me to see through the fog.

namuh1
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 17085004
the destroy method of the datamodule calls "stopwork" but i forgot to include it in my pasting
so when the service/manual form frees the datamodule, the server stops first
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now