Search code examples
oopdelphiwindows-services

In Delphi, how do I enable a method pointer property in both a base class and its descendant classes


I want to create a TService descendant class that I use as the basis for my Windows service implementations. In my base class I am introducing a published ServiceDescription property, and I am using the AfterInstall event handler to write this description to the appropriate location in the Windows registry.

Note that since the TServer class (declared in Vcl.SvcMgr) is a TDataModule descendant, in order to permit the ServiceDescription property to be visible in the Object Inspector it is necessary to declare this base class in a designtime package, and register it with Delphi using a call to RegisterCustomModule. In addition, a descendant of this base class must be generated by an OTA (open tools api) wizard or some sort of code generator (both .pas and .dfm files). No problem, I've got that one sorted, and if you're interested you can read more about it from Marco Cantu's book (http://www.marcocantu.com/ddh/ddh15/ddh15e.htm).

Where I’m stuck is that I want to use the AfterInstall event handler in my base class to write to the Registry, and the AfterUninstall to remove it, but I want to ensure that my descendant classes will also support AfterInstall and AfterUninstall events.

I previously learned from Ray Konopka that if you want to reintroduce a property that you must use accessor methods in the descendant class. As a result, here is a code segment that represents my attempt to do this with respect to the AfterInstall event:

  private
    // field to store method pointer for the descendant AfterInstall event handler
    FFAfterInstall: TServiceEvent;
    …
  protected
    function GetAfterInstall: TServiceEvent;
    procedure SetAfterInstall( value: TServiceEvent );
    …
  published
    property AfterInstall: TServiceEvent read GetAfterInstall write SetAfterInstall;

My overridden constructor assigns a method to the inherited AfterInstall property:

constructor TTPMBaseService.Create(AOwner: TComponent);
begin
  inherited;
  // Hook-up the AfterInstall event handlers
  Self.AfterInstall := CallAfterInstall;
  …
end;

In my implementation of CallAfterInstall, after I run my code to write to the Windows Registry, I test to see if a method pointer has been assigned to my local method pointer field, and if so, I call it. It looks something like this:

procedure TTPMBaseService.CallAfterInstall(Service: TService);
var
  Reg: TRegistry;
begin
  // Code here to write to the Windows Registry is omitted
  // Test if our method pointer field has been written to
  if Assigned( FFAfterInstall ) then
    FFAfterInstall( Service );  // call the method,
  …
end;

I think that this all makes a lot of sense, and I think it should work. However, I’m stuck on the accessor methods. The Get accessor method compiles just fine, and here it is:

function TTPMBaseService.GetAfterInstall: TServiceEvent;
begin
  Result := FFAfterInstall;
end;

But my SetAfterInstall method raises a compile-time exception, reporting that there are not enough parameters:

procedure TTPMBaseService.SetAfterInstall( value: TServiceEvent );
begin
  if value <> FFAfterInstall then
    FFAfterInstall := value;
end;

I’m not sure what to do here. I made the following change, and it compiles, but it does not appear to do the job:

procedure TTPMBaseService.SetAfterInstall( value: TServiceEvent);
begin
  if @value <> @FFAfterInstall then
    @FFAfterInstall := @value;
end;

I have two questions. The first is, am I taking the correct approach to reintroducing an event handler, all the while ensuring that both my base class, as well as its descendants, support this event? If my logic is correct, what am I doing wrong with the Setter accessor method?


Solution

  • Am I taking the correct approach to reintroducing an event handler?

    Probably yes. Thanks to clumsy design of TService class you're not able to override a method that raises the event.

    What am I doing wrong with the Setter accessor method?

    The problem is in fact in your constructor:

    constructor TTPMBaseService.Create(AOwner: TComponent);
    begin
      inherited;
      // Hook-up the AfterInstall event handlers
      Self.AfterInstall := CallAfterInstall;
    …
    end;
    

    The comment in it indicates that you're setting inherited event handler, but that's not what the code below the comment does. Despite of assigning to Self.AfterInstall you're setting the value of reintroduced property. This is how you set inherited property:

    constructor TTPMBaseService.Create(AOwner: TComponent);
    begin
      inherited;
      // Hook-up the AfterInstall event handlers
      inherited AfterInstall := CallAfterInstall;
    …
    end;
    

    But my SetAfterInstall method raises a compile-time exception, reporting that there are not enough parameters.

    You're getting syntax error on if statement in the setter method to be precise. It's simply because that's not how you compare method references in Delphi. See How to check if two events are pointing to the same procedure in Delphi. Why do you even need to perform such comparison? You can safely omit it.