I need to fix a third-party component. This component's class has private variable which is actively used by its descendants:
TThirdPartyComponentBase = class
private
FSomeVar: Integer;
public
...
end;
TThirdPartyComponent = class (TThirdPartyComponentBase)
protected
procedure Foo; virtual;
end;
procedure TThirdPartyComponent.Foo;
begin
FSomeVar := 1; // ACCESSING PRIVATE FIELD!
end;
This works because both classes are in the same unit, so they're kinda "friends".
But if I'll try to create a new class in a new unit
TMyFixedComponent = class (TThirdPartyComponent)
procedure Foo; override;
end;
I can't access FSomeVar anymore, but I need to use it for my fix. And I really don't want to reproduce in my code all that tree of base classes.
Can you advise some quick hack to access that private field without changing the original component's unit if it's possible at all?
By the use of class helpers it's possible to accomplish access to the private parts of the base class from the derived class without loosing type safety.
Just add these declarations in another unit:
Uses YourThirdPartyComponent;
type
// A helper to the base class to expose FSomeVar
TMyBaseHelper = class helper for TThirdPartyComponentBase
private
procedure SetSomeVar( value : integer);
function GetSomeVar: integer;
public
property SomeVar:integer read GetSomeVar write SetSomeVar;
end;
TMyFixedComponent = class helper for TThirdPartyComponent
protected
procedure Foo;
end;
procedure TMyFixedComponent.Foo;
begin
// Cast to base class and by the class helper TMyBaseHelper the access is resolved
TThirdPartyComponentBase(Self).SomeVar := 1;
end;
function TMyBaseHelper.GetSomeVar: integer;
begin
Result := Self.FSomeVar; // ACCESSING PRIVATE FIELD!
end;
procedure TMyBaseHelper.SetSomeVar(value: integer);
begin
Self.FSomeVar := value; // ACCESSING PRIVATE FIELD!
end;
// Testing
var
TSV: TThirdPartyComponent;
begin
TSV := TThirdPartyComponent.Create;
try
TSV.Foo;
WriteLn(IntToStr(TSV.SomeVar)); // Writes 1
finally
TSV.Free;
end;
end.
As can be seen from comments in code, FSomeVar is exposed by a class helper from the TThirdPartyComponentBase class.
Another class helper for the TThirdPartyComponent implements the Foo procedure. In there, access to the SomeVar property of the base class helper is made via a type cast to the base class.
You have to use a hack to access a private field in any class (including a base class) in a different unit. In your case define in your unit:
type
__TThirdPartyComponentBase = class
private
FSomeVar: Integer;
end;
Then get the access:
__TThirdPartyComponentBase(Self).FSomeVar := 123;
Of course, that is dangerous, because you will need to control changes in the base class. Because if the fields layout will be changed and you will miss this fact, then the above approach will lead to failures, AV's, etc.
Don't know if this will help, but I seem to recall there is a way to "crack" a private variable into visibility.
I know, for example, I've encountered warnings from the compiler when I've moved a property from lower visibility (in the base class) to a more visible level (in my descendant). The warning stated that it's being declared at a different level of visibility...
It's been some time and I'm not certain, but I believe what you can do is in your descendant declare the same variable as protected. (You may have to use the Redeclare keyword for this to compile.)
Sorry I don't have more specific information on how to do this (if it's indeed possible.) Perhaps this posting will prompt one of the wizards here into correcting me! :-)
Expose the value of the private variable by a protected property in TThirdPartyComponent.
TThirdPartyComponent = class (TThirdPartyComponentBase)
private
Procedure SetValue(Value: Integer);
Function GetValue: Integer;
protected
Property MyVar: Integer read GetValue write Setvalue;
procedure Foo; virtual;
end;
Procedure TThirdPartyComponent.SetValue(Value: Integer);
begin
FSomeVar := Value ;
end;
Function GetValue: Integer;
begin
result := FSomeVar;
end;
In TMyFixedComponent class use the MyVar Property in the procedure which you would like to override.
Related
I'm having problems with my Delphi 2006 seeming to call the incorrect constructor during dynamic creation.
I asked almost the exact same question 5 yrs ago (Why does Delphi call incorrect constructor during dynamic object creation?), and I have reviewed that. But that thread had issues of overriding virtual calls which I don't have now. I have also tried searching through StackOverflow for a matching question, but couldn't find an answer.
I am working with legacy code, so I didn't write much of this. (If you see comments below with '//kt' adding something, that is me).
The code has base class, TPCEItem as follow. Note that it does NOT have a constructor.
TPCEItem = class(TObject)
{base class for PCE items}
private
<irrelevent stuff>
public
<irrelevent stuff>
end;
Next, there is class type to use for passing a parameter (more below).
TPCEItemClass = class of TPCEItem;
Next I have a child class as follows. Note that it DOES have a contructor. The compiler will not allow me to add 'override' to this create method because the ancestor class where this is declared (TObject) does not define it as virtual.
TPCEProc = class(TPCEItem)
{class for procedures}
protected
<irrelevent stuff>
public
<irrelevent stuff>
constructor Create;
destructor Destroy; override;
end;
The code then has a function for copying data, which is a conglomeration of descendant types. Because this is older code, mosts of these lists are plain TLists or TStringLists, holding untyped pointers. Thus for each copy command a corresponding type is passed in for correct use.
procedure TPCEData.CopyPCEData(Dest: TPCEData);
begin
Dest.Clear;
<irrelevent stuff>
CopyPCEItems(FVisitTypesList, Dest.FVisitTypesList, TPCEProc); //kt added
CopyPCEItems(FDiagnoses, Dest.FDiagnoses, TPCEDiag);
CopyPCEItems(FProcedures, Dest.FProcedures, TPCEProc);
CopyPCEItems(FImmunizations, Dest.FImmunizations, TPCEImm);
CopyPCEItems(FSkinTests, Dest.FSkinTests, TPCESkin);
CopyPCEItems(FPatientEds, Dest.FPatientEds, TPCEPat);
CopyPCEItems(FHealthFactors, Dest.FHealthFactors, TPCEHealth);
CopyPCEItems(FExams, Dest.FExams, TPCEExams);
<irrelevent stuff>
end;
This CopyPCEItems is as follows:
procedure TPCEData.CopyPCEItems(Src: TList; Dest: TObject; ItemClass: TPCEItemClass);
var
AItem: TPCEItem;
i: Integer;
IsStrings: boolean;
Obj : TObject;
begin
if (Dest is TStrings) then begin
IsStrings := TRUE
end else if (Dest is TList) then begin
IsStrings := FALSE
end else begin
exit;
end;
for i := 0 to Src.Count - 1 do begin
Obj := TObject(Src[i]);
if(not TPCEItem(Src[i]).FDelete) then begin
AItem := ItemClass.Create; //<--- THE PROBLEMATIC LINE
if (Obj.ClassType = TPCEProc) and (ItemClass = TPCEProc) then begin //kt added if block and sub block below
TPCEProc(Obj).CopyProc(TPCEProc(AItem));
end else begin
AItem.Assign(TPCEItem(Src[i])); //kt <-- originally this line was by itself.
end;
if (IsStrings) then begin
TStrings(Dest).AddObject(AItem.ItemStr, AItem)
end else begin
TList(Dest).Add(AItem);
end;
end;
end;
end;
The problematic line is as below:
AItem := ItemClass.Create;
When I step through the code with the debugger, and stop on this line, an inspection of the variable ItemClass is as follows
ItemClass = TPCEProc
The problems is that the .Create is calling TObject.Create, not TPCEProc.Create, which doesn't give me an opportunity to instantiate some needed TStringLists, and later leads to access violation error.
Can anyone help me understand what is going on here? I have a suspicion that the problem is with this line:
TPCEItemClass = class of TPCEItem;
It is because this is of a class of an ancestor type (i.e. TPCEItem), that it doesn't properly carry the information for the child type (TPCEProc)?? But if this is true, then why does the debugger show that ItemClass = TPCEProc??
How can I effect a call to TPCEProc.Create?
I have been programming in Delphi for at least 30 yrs, and it frustrates me that I keep having problems with polymorphism. I have read about this repeatedly. But I keep hitting walls.
Thanks in advance.
When you are constructing objects through meta-class you need to mark its base class constructor as virtual, and if you need a constructor in any of the descendant classes they need to override that virtual constructor.
If the base class does not have a constructor, you will need to add empty one.
TPCEItem = class(TObject)
public
constructor Create; virtual;
end;
TPCEItemClass = class of TPCEItem;
TPCEProc = class(TPCEItem)
public
constructor Create; override;
destructor Destroy; override;
end;
constructor TPCEItem.Create;
begin
// if the descendant class is TObject
// or any other class that has empty constructor
// you can omit inherited call
inherited;
end;
You have already identified the problem - the base class TPCEItem does not define a virtual constructor, it just inherits a constructor from TObject, which is not virtual.
As such, you cannot create instances of any TPCEItem-derived classes by using your TPCEItemClass metaclass type. In order for a metaclass to invoke the correct derived class constructor, the base class being referred to MUST have a virtual constructor, eg:
TPCEItem = class(TObject)
...
public
constructor Create; virtual;
end;
TPCEProc = class(TPCEItem)
...
public
constructor Create; override;
...
end;
procedure TPCEData.CopyPCEItems(...; ItemClass: TPCEItemClass);
var
AItem: TPCEItem;
...
begin
...
AItem := ItemClass.Create; // <-- THIS WORKS NOW!
...
if (Obj is TPCEProc) then begin // <-- FYI: use 'is' rather than ClassType to handle descendants of TPCEProc...
TPCEProc(Obj).CopyProc(TPCEProc(AItem));
...
end;
Congratulations you have identified the problematic line
AItem := ItemClass.Create; //<--- THE PROBLEMATIC LINE
But what is wrong with this line? You are calling constructor method from existing class instance. You should not do this ever. You should only call constructor methods from specific class types not existing class instances.
So in order to fix your code change the mentioned line to
AItem := TPCEItem.Create;
You may be thinking of perhaps calling AItem := TPCEItemClass.Create; since above in your code you made next declaration
TPCEItemClass = class of TPCEItem;
This declaration does not meant that TPCEItemClass is the same type as TPCEItem but instead that both types have same type structure but they are in fact two distinct types.
By the way what is the purpose of ItemClass: TPCEItemClass parameter of your CopyPCEItems procedure if you are not even using it in your procedure but instead work with local variable AItem: TPCEItem all the time? Well at least in your shown code that is.
i have a class which contains another class.
Is it possible in Delphi to directly access the properties of the member class?
TNameValue = class
private
FSubName: string;
FSubValue: Integer;
public
property SubName: string read FSubName write FSubName;
property SubValue: Integer read FSubValue write FSubValue;
end;
TParentclass = class(TSomeotherclass)
FNameValue: TNameValue;
public
property NameValue: TNameValue read FNameValue write FNameValue;
end;
procedure TForm.Buttonclick();
begin
Parentclass := TParentclass.Create();
// here i would need to directly access the Property of the member class.
Showmessage(Parentclass.Subname);
end;
I know that i could make properties for alle the properties of the subclass that i want to access, but i have this class in multiple other classes and i don't want to change the code everywhere when the subclass changes.
Is there a way to define the property to publish its properties directly?
I know that i can access it using Parentclass.NameValue.Subname but i want to use it without the additional step of NameValue.
Is there a way to define the property to publish its properties directly?
No this is not possible as you would need multi inheritance to achieve this and Delphi does not support it. Either rework your class design or go through the hassle of implementing the needed properties.
Delphi doesn't automatically recognise that.
But you can help it.
constructor TParentClass.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
fSubClass := TSubClass.Create(Self);
fSubClass.SetSubComponent(True);
end;
like this you create a compound component
a component containing another component
changed from here:
unit uSubClass;
uses Classes;
type
TSubClass = class(TObject)
private
fProp: string;
protected
procedure SetProp(const aValue: string);
function GetProp: string;
public
property Prop: string read GetProp write SetProp;
end;
var SingleSubClass: TSubclass;
implmentation
procedure SetProp(const aValue: string);
begin
fProp := aValue;
end;
function GetProp: string;
begin
Result := fProp;
end;
initialization
SingleSubClass := TSubClass.Create;
finalization
SingleSubClass.Free;
end;
that SingleSubClass is now a global variable and can be accessed in another object.
procedure TForm123.Button1Click(Sender: TObject);
begin
ShowMessage(SingleSubClass.Prop);
end;
if you want other objects to be notified of a change on it, you'll have to add an observer pattern to it and register all interested objects for the changes
https://sourcemaking.com/design_patterns/observer/delphi
I would like to use Gabriel Corneanu's jpegex, a class helper for jpeg.TJPEGImage.
Reading this and this I've learned that beyond Delphi Seattle you cannot access private fields anymore like jpegex does (FData in the example below). Poking around with the VMT like David Heffernan proposed is far beyond me. Is there any easier way to get this done?
type
// helper to access TJPEGData fields
TJPEGDataHelper = class helper for TJPEGData
function Data: TCustomMemoryStream; inline;
procedure SetData(D: TCustomMemoryStream);
procedure SetSize(W,H: integer);
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := self.FData;
end;
Today I found a neat way around this bug using the with statement.
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
Besides that Embarcadero did a nice job building walls to protect the private parts and that's probably why they named it 10.1 Berlin.
Beware! This is a nasty hack and can fail when the internal field structure of the hacked class changes.
type
TJPEGDataHack = class(TSharedImage)
FData: TCustomMemoryStream; // must be at the same relative location as in TJPEGData!
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := TJPEGDataHack(self).FData;
end;
This will only work if the parent class of the "hack" class is the same as the parent class of the original class. So, in this case, TJPEGData inherits from TSharedImage and so does the "hack" class. The positions also need to match up so if there was a field before FData in the list then an equivalent field should sit in the "hack" class, even if it's not used.
A full description of how it works can be found here:
Hack #5: Access to private fields
By using a combination of a class helper and RTTI, it is possible to have the same performance as previous Delphi versions using class helpers.
The trick is to resolve the offset of the private field at startup using RTTI, and store that inside the helper as a class var.
type
TBase = class(TObject)
private // Or strict private
FMemberVar: integer;
end;
type
TBaseHelper = class helper for TBase // Can be declared in a different unit
private
class var MemberVarOffset: Integer;
function GetMemberVar: Integer;
procedure SetMemberVar(value: Integer);
public
class constructor Create; // Executed automatically at program start
property MemberVar : Integer read GetMemberVar write SetMemberVar;
end;
class constructor TBaseHelper.Create;
var
ctx: TRTTIContext;
begin
MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;
function TBaseHelper.GetMemberVar: Integer;
begin
Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;
procedure TBaseHelper.SetMemberVar(value: Integer);
begin
PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;
As you can see it requires a bit of extra typing, but compared to patching a whole unit, it is simple enough.
In Delphi 10 Seattle I could use the following code to work around overly strict visibility restrictions.
How do I get access to private variables?
type
TBase = class(TObject)
private
FMemberVar: integer;
end;
And how do I get access to plain or virtual private methods?
type
TBase2 = class(TObject)
private
procedure UsefullButHidden;
procedure VirtualHidden; virtual;
procedure PreviouslyProtected; override;
end;
Previously I would use a class helper to break open the base class.
type
TBaseHelper = class helper for TBase
function GetMemberVar: integer;
In Delphi 10.1 Berlin, class helpers no longer have access to private members of the subject class or record.
Is there an alternative way to access private members?
If there is extended RTTI info generated for the class private members - fields and/or methods you can use it to gain access to them.
Of course, accessing through RTTI is way slower than it was through class helpers.
Accessing methods:
var
Base: TBase2;
Method: TRttiMethod;
Method := TRttiContext.Create.GetType(TBase2).GetMethod('UsefullButHidden');
Method.Invoke(Base, []);
Accessing variables:
var
Base: TBase;
v: TValue;
v := TRttiContext.Create.GetType(TBase).GetField('FMemberVar').GetValue(Base);
Default RTTI information generated for RTL/VCL/FMX classes is following
Fields - private, protected, public, published
Methods - public, published
Properties - public, published
Unfortunately, that means accessing private methods via RTTI for core Delphi libraries is not available. #LU RD's answer covers hack that allows private method access for classes without extended RTTI.
Working with RTTI
There is still a way to use class helpers for access of private methods in Delphi 10.1 Berlin:
type
TBase2 = class(TObject)
private
procedure UsefullButHidden;
procedure VirtualHidden; virtual;
procedure PreviouslyProtected; override;
end;
TBase2Helper = class helper for TBase2
procedure OpenAccess;
end;
procedure TBase2Helper.OpenAccess;
var
P : procedure of object;
begin
TMethod(P).Code := #TBase2.UsefullButHidden;
TMethod(P).Data := Self;
P; // Call UsefullButHidden;
// etc
end;
Unfortunately there is no way to access strict private/private fields by class helpers with Delphi 10.1 Berlin. RTTI is an option, but can be considered slow if performance is critical.
Here is a way to define the offset to a field at startup using class helpers and RTTI:
type
TBase = class(TObject)
private // Or strict private
FMemberVar: integer;
end;
type
TBaseHelper = class helper for TBase
private
class var MemberVarOffset: Integer;
function GetMemberVar: Integer;
procedure SetMemberVar(value: Integer);
public
class constructor Create; // Executed at program start
property MemberVar : Integer read GetMemberVar write SetMemberVar;
end;
class constructor TBaseHelper.Create;
var
ctx: TRTTIContext;
begin
MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;
function TBaseHelper.GetMemberVar: Integer;
begin
Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;
procedure TBaseHelper.SetMemberVar(value: Integer);
begin
PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;
This will have the benefit that the slow RTTI part is only executed once.
Note: Using RTTI for access of protected/private methods
The RTL/VCL/FMX have not declared visibility for access of protected/private methods with RTTI. It must be set with the local directive {$RTTI}.
Using RTTI for access of private/protected methods in other code requires for example setting :
{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}
If you want a clean way that does not impact performance, you still can access private fields from a record helper using the with statement.
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
I hope they keep this method open, because we have code with high performance demands.
Assuming that extended RTTI is not available, then without resorting to what would be considered hacking, you cannot access private members from code in a different unit. Of course, if RTTI is available it can be used.
It is my understanding that the ability to crack private members using helpers was an unintentional accident. The intention is that private members only be visible from code in the same unit, and strict private members only be visible from code in the same class. This change corrects the accident.
Without the ability to have the compiler crack the class for you, you would need to resort to other ways to do so. For instance, you could re-declare enough of the TBase class to be able to trick the compiler into telling you where a member lived.
type
THackBase = class(TObject)
private
FMemberVar: integer;
end;
Now you can write
var
obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;
But this is horrendously brittle and will break as soon as the layout of TBase is changed.
That will work for data members, but for non-virtual methods, you'd probably need to use runtime disassembly techniques to find the location of the code. For virtual members this technique can be used to find the VMT offset.
Further reading:
http://hallvards.blogspot.nl/2004/06/hack-5-access-to-private-fields.html
https://bitbucket.org/NickHodges/delphi-unit-tests/wiki/Accessing%20Private%20Members
If you don't need ARM compiler support, you can find another solution here.
With inline asembler, you can access private field or method, easily.
I think David's answer is better in most case, but if you need a quick solution for a huge class, this method could be more useful.
Update(June 17): I've just noticed, I forgot to share his sample code for accessing private fields from his post. sorry.
unit UnitA;
type
THoge = class
private
FPrivateValue: Integer;
procedure PrivateMethod;
end;
end.
unit UnitB;
type
THogeHelper = class helper for THoge
public
function GetValue: Integer;
procedure CallMethod;
end;
function THogeHelper.GetValue: Integer;
asm
MOV EAX,Self.FPrivateValue
end;
procedure THogeHelper.CallMethod;
asm
CALL THoge.PrivateMethod
end;
Here is his sample code for calling private method.
type
THoge = class
private
procedure PrivateMethod (Arg1, Arg2, Arg3 : Integer);
end;
// Method 1
// Get only method pointer (if such there is a need to assign a method pointer to somewhere)
type
THogePrivateProc = procedure (Self: THoge; Arg1, Arg2, Arg3: Integer);
THogePrivateMethod = procedure (Arg1, Arg2, Arg3: Integer) of object;
function THogeHelper.GetMethodAddr: Pointer;
asm
{$ifdef CPUX86}
LEA EAX, THoge.PrivateMethod
{$else}
LEA RAX, THoge.PrivateMethod
{$endif}
end;
var
hoge: THoge;
proc: THogePrivateProc;
method: THogePrivateMethod;
begin
// You can either in here of the way,
proc := hoge.GetMethodAddr;
proc (hoge, 1, 2, 3);
// Even here of how good
TMethod (method) .Code := hoge.GetMethodAddr;
TMethod (method) .Data := hoge;
method (1, 2, 3) ;
end;
// Method 2
// To jump (here is simple if you just simply call)
procedure THogeHelper.CallMethod (Arg1, Arg2, Arg3 : Integer);
asm
JMP THoge.PrivateMethod
end;
unit UnitA;
type
THoge = class
private
FPrivateValue: Integer;
procedure PrivateMethod;
end;
end.
Just use 'with' statement to access private fields !
See the sample code below, taken from this article I noticed today. (Thanks, Mr.DEKO as always !)
This hack is originally reported on QualityPortal in August 2019 as described on above aritcle. (login required)
before rewrite (using "asm" method)
function TPropertyEditorHelper.GetPropList: PInstPropList;
{$IF CompilerVersion < 31.0}
begin
Result := Self.FPropList;
end;
{$ELSE}
// http://d.hatena.ne.jp/tales/20160420/1461081751
asm
MOV EAX, Self.FPropList;
end;
{$IFEND}
rewrite using 'with'
function TPropertyEditorHelper.GetPropList: PInstPropList;
begin
with Self do
Result := FPropList;
end;
I was amazed it's so simple :-)
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?
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.
Last year, Hallvard Vassbotn blogged about a Delphi-hack I had made for this, it became a two-part article:
Hack#17: Virtual class variables, Part I
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.
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;
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.
Well, it's not beauty, but works fine in Delphi 7:
TMyObject = class
pulic
class function MySharedObject: TMySharedObject; // I'm lazy so it will be read only
end;
implementation
...
class function MySharedObject: TMySharedObject;
{$J+} const MySharedObjectInstance: TMySharedObject = nil; {$J-} // {$J+} Makes the consts writable
begin
// any conditional initialization ...
if (not Assigned(MySharedObjectInstance)) then
MySharedObjectInstance = TMySharedOject.Create(...);
Result := MySharedObjectInstance;
end;
I'm curently using it to build singletons objects.
For what I want to do (a private class constant), the neatest solution that I can come up with (based on responses so far) is:
unit MyObject;
interface
type
TMyObject = class
private
class var FLogger: TLogLogger;
end;
implementation
initialization
TMyObject.FLogger:= TLogLogger.GetLogger(TMyObject);
finalization
// You'd typically want to free the class objects in the finalization block, but
// TLogLoggers are actually managed by Log4D.
end.
Perhaps a little more object oriented would be something like:
unit MyObject;
interface
type
TMyObject = class
strict private
class var FLogger: TLogLogger;
private
class procedure InitClass;
class procedure FreeClass;
end;
implementation
class procedure TMyObject.InitClass;
begin
FLogger:= TLogLogger.GetLogger(TMyObject);
end;
class procedure TMyObject.FreeClass;
begin
// Nothing to do here for a TLogLogger - it's freed by Log4D.
end;
initialization
TMyObject.InitClass;
finalization
TMyObject.FreeClass;
end.
That might make more sense if there were multiple such class constants.
Two questions I think that need to be answered before you come up with a "perfect" solution..
The first, is whether TLogLogger is thread-safe. Can the same TLogLogger be called from multiple threads without calls to "syncronize"? Even if so, the following may still apply
Are class variables thread-in-scope or truly global?
If class variables are truly global, and TLogLogger is not thread safe, you might be best to use a unit-global threadvar to store the TLogLogger (as much as I don't like using "global" vars in any form), eg
Code:
interface
type
TMyObject = class(TObject)
private
FLogger: TLogLogger; //NB: pointer to shared threadvar
public
constructor Create;
end;
implementation
threadvar threadGlobalLogger: TLogLogger = nil;
constructor TMyObject.Create;
begin
if not Assigned(threadGlobalLogger) then
threadGlobalLogger := TLogLogger.GetLogger(TMyObject); //NB: No need to reference count or explicitly free, as it's freed by Log4D
FLogger := threadGlobalLogger;
end;
Edit: It seems that class variables are globally stored, rather than an instance per thread. See this question for details.
In Delphi static variables are implemented as variable types constants :)
This could be somewhat misleading.
procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
And yes, another possibility is using global variable in implementation part of your module.
This only works if the compiler switch "Assignable Consts" is turned on, globally or with {$J+} syntax (tnx Lars).
Before version 7, Delphi didn't have static variables, you'd have to use a global variable.
To make it as private as possible, put it in the implementation section of your unit.