Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class/Static Constants in Delphi

Tags:

delphi

In Delphi, I want to be able to create an private object that's associated with a class, and access it from all instances of that class. In Java, I'd use:

public class MyObject {
    private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}

Or, if MySharedObject needed more complicated initialization, in Java I could instantiate and initialize it in a static initializer block.

(You might have guessed... I know my Java but I'm rather new to Delphi...)

Anyway, I don't want to instantiate a new MySharedObject each time I create an instance of MyObject, but I do want a MySharedObject to be accessible from each instance of MyObject. (It's actually logging that has spurred me to try to figure this out - I'm using Log4D and I want to store a TLogLogger as a class variable for each class that has logging functionality.)

What's the neatest way to do something like this in Delphi?

like image 996
MB. Avatar asked Sep 16 '08 12:09

MB.


4 Answers

Here is how I'll do that using a class variable, a class procedure and an initialization block:

unit MyObject;

interface

type

TMyObject = class
   private
     class var FLogger : TLogLogger;
   public
     class procedure SetLogger(value:TLogLogger);
     class procedure FreeLogger;
   end;

implementation

class procedure TMyObject.SetLogger(value:TLogLogger);
begin
  // sanity checks here
  FLogger := Value;
end;

class procedure TMyObject.FreeLogger;
begin
  if assigned(FLogger) then 
    FLogger.Free;
end;

initialization
  TMyObject.SetLogger(TLogLogger.Create);
finalization
  TMyObject.FreeLogger;
end.
like image 85
Pierre-Jean Coudert Avatar answered Sep 16 '22 19:09

Pierre-Jean Coudert


Last year, Hallvard Vassbotn blogged about a Delphi-hack I had made for this, it became a two-part article:

  1. Hack#17: Virtual class variables, Part I
  2. Hack#17: Virtual class variables, Part II

Yeah, it's a long read, but very rewarding.

In summary, I've reused the (deprecated) VMT entry called vmtAutoTable as a variable. This slot in the VMT can be used to store any 4-byte value, but if you want to store, you could always allocate a record with all the fields you could wish for.

like image 24
PatrickvL Avatar answered Sep 16 '22 19:09

PatrickvL


 TMyObject = class
    private
      class var FLogger : TLogLogger;
      procedure SetLogger(value:TLogLogger);
      property Logger : TLogLogger read FLogger write SetLogger;
    end;

procedure TMyObject.SetLogger(value:TLogLogger);
begin
  // sanity checks here
  FLogger := Value;
end;

Note that this class variable will be writable from any class instance, hence you can set it up somewhere else in the code, usually based on some condition (type of logger etc.).

Edit: It will also be the same in all descendants of the class. Change it in one of the children, and it changes for all descendant instances. You could also set up default instance handling.

 TMyObject = class
    private
      class var FLogger : TLogLogger;
      procedure SetLogger(value:TLogLogger);
      function GetLogger:TLogLogger;
      property Logger : TLogLogger read GetLogger write SetLogger;
    end;

function TMyObject.GetLogger:TLogLogger;
begin
  if not Assigned(FLogger)
   then FLogger := TSomeLogLoggerClass.Create;
  Result := FLogger;
end;

procedure TMyObject.SetLogger(value:TLogLogger);
begin
  // sanity checks here
  FLogger := Value;
end;
like image 27
Lars Fosdal Avatar answered Sep 16 '22 19:09

Lars Fosdal


The keywords you are looking for are "class var" - this starts a block of class variables in your class declaration. You need to end the block with "var" if you wish to include other fields after it (otherwise the block may be ended by a "private", "public", "procedure" etc specifier). Eg

(Edit: I re-read the question and moved reference count into TMyClass - as you may not be able to edit the TMySharedObjectClass class you want to share, if it comes from someone else's library)

  TMyClass = class(TObject)
  strict private
    class var
      FMySharedObjectRefCount: integer;
      FMySharedObject: TMySharedObjectClass;
    var
    FOtherNonClassField1: integer;
    function GetMySharedObject: TMySharedObjectClass;
  public
    constructor Create;
    destructor Destroy; override;
    property MySharedObject: TMySharedObjectClass read GetMySharedObject;
  end;


{ TMyClass }
constructor TMyClass.Create;
begin
  if not Assigned(FMySharedObject) then
    FMySharedObject := TMySharedObjectClass.Create;
  Inc(FMySharedObjectRefCount);
end;

destructor TMyClass.Destroy;
begin
  Dec(FMySharedObjectRefCount);
  if (FMySharedObjectRefCount < 1) then
    FreeAndNil(FMySharedObject);

  inherited;
end;

function TMyClass.GetMySharedObject: TMySharedObjectClass;
begin
  Result := FMySharedObject;
end;

Please note the above is not thread-safe, and there may be better ways of reference-counting (such as using Interfaces), but this is a simple example which should get you started. Note the TMySharedObjectClass can be replaced by TLogLogger or whatever you like.

like image 24
Graza Avatar answered Sep 19 '22 19:09

Graza