Home  |  Products  |  Projects  |  Articles  |  Contact  

Articles

« Delphi subclassable
singleton class »
Apache/mod_perl
and XML-RPC

Creating a subclassable singleton class in Delphi

by Craig Manley, 1 december 2002 (updated 11 december 2006).

After searching for techniques to create singleton classes in Delphi, I found none that could be subclassed. See for example Creating a real singleton class in Delphi. I therefore decided to create such a class myself, roughly based on the example in that link.

The Singleton class' unit

Usage:
Below is the listing of this unit. To create a new singleton class, you must subclass this class and implement the CreateSingleton() method. This method is called automatically and only once - when the Create() constructor is called for the first time. Use this method to initialize your singleton object.

unit ssSingleton;

interface

uses SysUtils;

type
  EssSingletonException = class(Exception);

  TssSingleton = class(TObject)
  private
    class function RefCount: Cardinal;
  public
    constructor Create;
    class function NewInstance: TObject; override;
    procedure FreeInstance; override;
    procedure CreateSingleton; virtual; abstract;
  end;

implementation

uses Classes;

type
  // Define structure that contains the reference count and object pointer.
  PssSingletonInstance = ^TssSingletonInstance;
  TssSingletonInstance = record
    refcount: Cardinal;
    instance: TObject;
  end;

// The instances: classname => TssSingletonInstance pairs
var
  instances: TStrings;


procedure CleanupInstances;
var
  i: integer;
begin
  for i := instances.Count-1 downto 0 do begin
    if PssSingletonInstance(instances.Objects[i])^.refcount > 1 then begin
      PssSingletonInstance(instances.Objects[i]).instance.Free;
      if (i <= instances.Count) then begin
        Dispose(PssSingletonInstance(instances.Objects[i]));
        instances.Delete(i);
      end;
    end
    else
      PssSingletonInstance(instances.Objects[i]).instance.Free;
  end;
  instances.Free; // Added 2006-12-11, thanks to Graeme Geldenhuys.
end;


constructor TssSingleton.Create;
begin
  inherited;
  if (RefCount <= 1) then
    CreateSingleton;
end;


procedure TssSingleton.FreeInstance;
var
  i: integer;
  instance: PssSingletonInstance;
begin
  i := instances.IndexOf(ClassName);
  if i < 0 then begin // no instance found!
    inherited;
    Exit;
  end;
  instance := PssSingletonInstance(instances.Objects[i]);
  if (instance^.refcount > 1) then
    Dec(instance^.refcount)
  else begin
    inherited FreeInstance; // free instance here
    // free and remove the PssSingletonInstance from the list.
    Dispose(PssSingletonInstance(instances.Objects[i]));
    instances.Delete(i);
  end;
end;


class function TssSingleton.NewInstance: TObject;
var
  i: integer;
  instance: PssSingletonInstance;
begin
  i := instances.IndexOf(ClassName);
  if i >= 0 then begin // instance found
    instance := PssSingletonInstance(instances.Objects[i]);
  end
  else begin
    new(instance);
    instance^.instance := inherited NewInstance;
    instance^.refcount := 0;
    instances.AddObject(ClassName,TObject(instance));
  end;
  Inc(instance^.refcount);
  Result := instance^.instance;
end;


class function TssSingleton.RefCount: Cardinal;
var
  i: integer;
  instance: PssSingletonInstance;
begin
  i := instances.IndexOf(ClassName);
  if i >= 0 then begin // instance found
    instance := PssSingletonInstance(instances.Objects[i]);
    Result := instance^.refcount;
  end
  else
    raise EssSingletonException.Create('Class "' + ClassName +
                                       '" not found in instances list!');
end;


initialization
  instances := TStringList.Create;


finalization
  CleanupInstances;

end.

This is how it works:
The NewInstance() method overrides the same method in the parent class, TObject. This method must return an instance. Only on the 1st call to get a singleton instance from the Singleton class should the parent method be called (this method then actually allocates memory etc. to create a new instance). All subsequent calls must return the same instance, so in these cases, the parent method must not be called. Because it must return the same instance every time (that's singleton), the instance must be stored in a global variable somewhere in the unit, but because this instance must also be associated with the class it's being used in in order to prevent it from being shared amongst different classes with unpredictable results, it must be associated with the name of the class it was created in. To do this, all instances are stored in a TStrings and are pointed to by their class names. TStrings are useful for this because you can associate strings with objects (or any other 32 bit data such as pointers).
About threads:
This class is not thread safe just as much of the VCL isn't thread safe. Using this class in Delphi TThreads is safe though because they are designed to work with the non-thread-safe VCL. You can easily make this class thread safe by wrapping all tricky bits in critical sections, but for most Delphi programs, this isn't necessary and is just a (small) performance burden.

Two simple examples of subclasses of the Singleton class

Below are 2 listings. They are both singleton classes meant for storing and retrieving the name of a fruit or vegetable. These are of course not very useful classes but are only meant for demonstration anyway. Both classes descend from TSingleton and add a private field FData with and accessor property Data. I named these classes SingletonFruit and SingletonVeg respectively. All descendents of TSingleton must override the CreateSingleton() method. This method is called automatically from the constructor when the constructor is called for the first time.

unit SingletonFruit;

interface

uses ssSingleton;

type
  TSingletonFruit = class(TssSingleton)
  private
    FData: string;
  public
    procedure CreateSingleton; override;
    property Data: string read FData write FData;
  end;

implementation

procedure TSingletonFruit.CreateSingleton;
begin
  FData := 'Banana'; // the default fruit name
end;

end.
unit SingletonVeg;

interface

uses ssSingleton;

type
  TSingletonVeg = class(TssSingleton)
  private
    FData: string;
  public
    procedure CreateSingleton; override;
    property Data: string read FData write FData;
  end;

implementation

procedure TSingletonVeg.CreateSingleton;
begin
  FData := 'Sprouts'; // the default vegetable name
end;

end.

A simple usage example

Below is the listing of a test program to check if this concept really works.

program Project1;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  SingletonFruit in 'SingletonFruit.pas',
  SingletonVeg in 'SingletonVeg.pas';

var
  fruit1, fruit2: TSingletonFruit;
  veg1, veg2: TSingletonVeg;

begin
  fruit1 :=  TSingletonFruit.Create;
  fruit2 :=  TSingletonFruit.Create;
  veg1 := TSingletonVeg.Create;
  veg2 := TSingletonVeg.Create;

  fruit1.Data := 'Apple';
  fruit2.Data := 'Lemon';
  veg1.Data := 'Tomato';
  veg2.Data := 'Potato';

  writeln('Fruit 1 contains: ' + fruit1.Data);
  writeln('Fruit 2 contains: ' + fruit2.Data);
  writeln('Vegetable 1 contains: ' + veg1.Data);
  writeln('Vegetable 2 contains: ' + veg2.Data);
end.

Executing the program above results in this output as expected:

Fruit 1 contains: Lemon
Fruit 2 contains: Lemon
Vegetable 1 contains: Potato
Vegetable 2 contains: Potato

Some singleton ideas

The singleton class above is a module wide singleton. If your application consists of an EXE and a DLL, each with the same singleton class, then you'll end up with 2 singleton instances instead of 1 as you might expect.
To solve this problem, you can create a process wide singleton class by not storing all instances in a global variable such as "instances: TStrings;" as in my example, but in a memory mapped file (shared memory). You can also use this technique to create a system wide singleton class which can be shared amongst seperate processes on the same system.