Bypassing (disabling) Delphi's reference counting for interfaces - delphi

For one particular issue in the architecture of an application I'm working on, interfaces seem to be a nice solution. Specifically, some "business objects" depend on a bunch of settings that are pulled from the database in the actual app. Letting those business objects ask for an interface (through Inversion of Control), and letting a central TDatabaseSettings object implement those interfaces, allows for better isolation, and thus for much easier unit testing.
However, in Delphi, interfaces seem to come with an, in this case, unpleasant bonus: reference counting. This means that if I do something like this:
type
IMySettings = interface
function getMySetting: String;
end;
TDatabaseSettings = class(..., IMySettings)
//...
end;
TMyBusinessObject = class(TInterfacedObject, IMySettings)
property Settings: IMySettings read FSettings write FSettings;
end;
var
DatabaseSettings: TDatabaseSettings;
// global object (normally placed in a controller somewhere)
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
On the last line (O.Free), my global DatabaseSettings object is now also freed, since the last interface reference to it (which was contained in O) is lost!
One solution would be to store the 'global' DatabaseSettings object with an interface; another solution would be to override the reference counting mechanism for the TDatabaseSettings class, so I can continue to manage the DatabaseSettings as a normal object (which is much more consistent with the rest of the app).
So, in summary, my question is: how do I disable the interface reference counting mechanism for a particular class?
I've been able to find some info that suggests overriding the IInterface methods _AddRef and _Release for the class (TDatabaseSettings in the example); has anyone ever done that?
Or would you say I shouldn't do this (confusing? just a bad idea?), and find a different solution to the architectural problem?
Thanks a lot!

Ok, you can bypass it, but the question is if you really want that.
If you want to use interfaces, you better use them completely. So as you have experienced it, you get problems if you mix class and interface variables.
var
// DatabaseSettings: TDatabaseSettings;
DatabaseSettings : IMySettings;
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
You now have a second reference to the interface and losing the first will not free the object.
It as also possible to keep both the class and the object:
var
DatabaseSettings: TDatabaseSettings;
DatabaseSettingsInt : IMySettings;
Be sure to set the interface right after the object has been created.
If you really want to disable reference counting, you just have to create a new descendant of TObject that implements IInterface. I have tested the example below in D2009 and it works:
// Query Interface can stay the same because it does not depend on reference counting.
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
constructor TMyInterfacedObject.Create;
begin
FRefCount := 1;
end;
procedure TMyInterfacedObject.FreeRef;
begin
if Self = nil then
Exit;
if InterlockedDecrement(FRefCount) = 0 then
Destroy;
end;
function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
FreeRef just lowers the refcount just like _Release. You can use it where you normally use Free.

Don't descend from TInterfacedObject, instead descend from TSingletonImplementation from standard System.Generics.Defaults unit.
TSingletonImplementation is a base for simple classes that need a basic IInterface implementation, with reference counting disabled.
TSingletonImplementation is a thread-safe base class for Delphi classes that support interfaces. Unlike TInterfacedObject, TSingletonImplementation does not implement reference counting.

_AddRef, _Release and _QueryInterface are, in fact, what you want to override. You should be very clear about what you're doing, however, as this can cause memory leaks or strange, hard-to-find bugs.
Don't descend from TInterfacedObject, instead descend from TObject, and implement your own versions of the first two of those methods that return 1.

To disable reference counting, AddRef and Release should do nothing but return -1
function TMyInterfacedObject._AddRef: Integer;
begin
Result := -1;
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := -1;
end;
There is quite a lot of utility in interfaces without reference counting. If you use reference counting, then you cannot mix object and interface references as bad things will happen. By disabling ref counts, you can happily mix interface and object references without worrying about your objects suddenly getting auto destroyed.

Disabling reference counting for this kind of problem smells bad.
A much nicer and architectural solution would be to use some kind of "singleton" pattern.
The easiest way to implement this would look like:
interface
type
TDatabaseSettings = class(..., IMySettings)
end;
function DatabaseSettings: IMySettings;
implementation
var
GDatabaseSettings: IMySettings;
function DatabaseSettings: IMySettings;
begin
if GDatabaseSettings = nil then GDatabaseSettings := TDatabaseSettings.Create;
Result := GDatabaseSettings;
end;
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
O.Free;
By the way: when you use interfaces: always use interface variables! Do not mix both class en interface vars (use "var Settings: IMySettings" instead of "var Settings: TDatabaseSettings"). Otherwise reference counting will get in your way (auto destroy, invalid pointer operations, etc).
In the above solution, GDatabaseSettings is also of type "IMySettings", so it gets a proper reference count, and will last till your program terminates.

Or just use the code below:
var
I: IMyInterface;
begin
I := ...;
...
Do whatever you want in a scope;
Initialize(I); //- this will clear the interface variable without calling the _release.
end.

Related

Does this FreeAndNil(lAux) frees my double field too?

I'm correcting some (a lot) memory leaks of a project. I have some Delphi experience but is not my main language so I'm extra careful at freeing stuff.
I've already tried it and everything looks fine, but I thought that it would free the Field too (not what I want). Does that happen just with Objects?
TMainObject = class(TObject)
public
FDoubleField : double;
*
*
*
procedure TMainObject.CalculateSomeFieldValue();
var
lAux : TAuxObject;
begin
lAux := TAuxObject.Create;
lAux.RecoverData;
if **some condition** then
begin
FDoubleField := lAux.DoubleProperty;
end
else
begin
FDoubleField := lAux.OtherDoubleProperty;
end;
FreeAndNil(lAux);
end
Thanks in advance
Edit: I do not want to lose FDoubleField because I will use it later, but I need to free lAux.
The FDoubleField field doesn't need to be freed because it will be automatically "freed" as soon it goes out of scope.
Some tips:
You don't need to use FreeAndNil(lAux) in this case, just use lAux.Free because there's no need to set a pointer to nil which will no longer be used.
At the beginning, you can set ReportMemoryLeaksOnShutDown := True (documentation). In this way, you will be notified about any memory leak when the application closes.
Use try-finally blocks for being sure to always free the object you created
Modified code using try-finally block and Free:
procedure TMainObject.CalculateSomeFieldValue();
var
lAux : TAuxObject;
begin
lAux := TAuxObject.Create;
try
lAux.RecoverData;
if **some condition** then
begin
FDoubleField := lAux.DoubleProperty;
end
else
begin
FDoubleField := lAux.OtherDoubleProperty;
end;
finally
lAux.Free;
end;
end
In the example you give, you don't need to free anything for a variable of type double in a class (Also called a "field").
Field that must be freed are classes or simple pointers of any type that have been allocated.
The normal place to free data in a class is in the destructor.
Also pay attention to interfaces. They are generally reference counted and as such MUST NOT be freed. Frequently, interfaces are assigned a value by calling a class constructor and you must really look if the value returned by the class constructor is assigned to a variable of an interface type or to a variable of some object type. Assuming TAuxObject also implement the interface IAuxInterface, your have the following valid code:
var
AuxObj : TAuxObject;
AuxIntf : IAuxInterface;
begin
AuxIntf := TAuxObject.Create; // This one must NOT be freed
AuxObj := TAuxObject.Create; // This one must be freed
try
....
finally
AuxObj.FRee;
end;
end;
Properly using interfaces is a big subject. There are cases where interfaces are not reference counted.
There are in Delphi libraries a few classes designed to be ancestor for object supporting interface (For example TInterfacedObject). This ancestor implement reference counting properly (Methods QueryInterface, _AddRef and _Release). If your object do no derive from a class already implementing reference counting, then if you need reference counting then you must implement methods QueryInterface, _AddRef and _Release. As soon as they are defined, some compiler magic will call them when it is needed. And by the way freeing the object must actually done by the _Release method implementation.
Things becomes even more complex if the developer make use of weak and unsafe keywords to mark the interface type variable.

Instantiated COM Component gets invalid after leaving method (but not its scope)

I am currently testing two external COM components. I have big issue with one of them, but I cannot really find reason behind such behavior. Let me provide some example.
const
CLASS_SomeClas: TGUID = '{SomeGUID}';
type
ISomeInterface = interface(IDispatch)
['{SomeGUID}']
function SomeMethod(const AInput: WideString): WideString; safecall;
end;
TWrappingClass = class(TObject)
strict private
FInstance: ISomeInterface;
procedure CreateInstance;
public
procedure DoYourActualJob;
end;
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
dbg(FInstance._AddRef); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
dbg(FInstance.Release); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
FInstance.SomeMethod(''); //Runs as expected
end;
procedure TWrappingClass.DoYourActualJob;
begin
CreateInstance;
dbg(FInstance._AddRef); //Debugs -1!
FInstance.SomeMethod(''); //AV
end;
As provided with example instance gets invalid after it leaves CreateInstance method. Component is designed to work with many sequential calls of SomeMethod and it does work when called inside single method.
Could someone give me clue what is actually happening there, why my instance gets invalid? Is it problem with my code, with Delphi or with component's code? When I change the implementation of TWrappingClass to another vendor (that is I change both ISomeInterface and CLASS_SomeClass) then everything works fine.
EDIT:
Behaviour does not change when I don't even call SomeMethod. That is after I leave CreateInstance, call to _AddRef returns -1. Component I am testing is here CadEditorX Probably I am not allowed to attach the OCX without violating its license.
You state clearly in the question that the erroneous behaviour only occurs with one specific COM object. Given this fact, and that Delphi's COM reference counting is known to work correctly, the only reasonable conclusion is that the fault lies in this specific COM object.
Your only recourse of action is to contact the vendor of this COM object and file a bug report with them.
One thing to look at, with a view to a possible work around, is how you are creating the object. You use CreateComObject. This receives a class ID and returns IUnknown. It calls CoCreateInstance passing the class ID, and requesting the IUnknown interface. You then need to query for your interface, ISomeInterface. So your code looks like this:
var
iunk: IUnknown;
intf: ISomeInteface;
....
CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER,
IUnknown, iunk);
iunk.QueryInterface(ISomeInterface, intf);
The fact that you have two interface variables, one IUnknown and one ISomeInterface explains why you see the reference count that you do. Now, you might think that you only have one interface variable, but that's not the case. There are two, only one of them is an implicit local. You can see this by looking at the compiled code and stepping through under the debugger.
This code:
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
end;
is compiled as if it were this (ignoring error checking):
procedure TWrappingClass.CreateInstance;
var
iunk: IUnknown;
begin
iunk := CreateComObject(CLASS_SomeClass);
try
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
finally
iunk := nil;
end;
end;
Perhaps the COM component cannot handle the call to Release made on its IUnknown interface.
So, you could try to work around this by using CoCreateInstance instead of CreateComObject. Pass ISomeInterface as the riid parameter.
OleCheck(CoCreateInstance(CLASS_SomeClass, nil, CLSCTX_INPROC_SERVER
or CLSCTX_LOCAL_SERVER, ISomeInterface, FInstance));

Cast from interface to class fails when introducing additional name for interface - why?

I stumbled upon a case where hard-casting from interface to class fails under certain circumstances.
Consider the following type definitions:
IDummy<T> = interface
end;
TMyRecord = record
Intf:IDummy<Byte>;
end;
TDummy = class(TInterfacedObject, IDummy<Byte>)
public
end;
IThisBreaksIt = IDummy<Byte>; // <== this line triggers the error
And now the simple code that uses the types:
var
ARecord:TMyRecord;
Item:IDummy<Byte>;
ImplWorks,
ImplBroken:TDummy;
begin
ARecord.Intf:=TDummy.Create;
Item:=ARecord.Intf;
ImplWorks:=TDummy(Item);
ImplBroken:=TDummy(ARecord.Intf); // <== resulting instance is broken
end;
So what I am doing is storing an interface reference inside a record. Now I want to cast this back to the implementing class with a hard cast.
Here is the catch: this fails if I define an alias for my interface (IThisBreaksIt = IDummy<Byte>). Comment out this line and the ImplBrokenis not broken anymore. In the broken case the addresses of ImplWorks and ImplBroken are different; instead the addresses of Item and ImplBroken are now the same. It seems like the automagic responsible for hard-casting fails to kick in.
Additional finding: Replacing TDummy(ARecord.Intf) by ARecord.Intf as TDummy fixes it.
This gave me some headache because it was buried in a bunch of code and I wasn't expecting this behavior. Is this normal?
Edit for Cosmin:
Example for working hard cast of interface to object.
Tested in XE: works (the pointers of StreamAdaptIntf and StreamAdaptImpl differ; Assertion succeeds)
Tested in 2009: fails (the pointers of StreamAdaptIntf and StreamAdaptImpl are the same; Assertion fails)
uses ActiveX;
var
Stream:TStream;
StreamAdaptIntf:IStream;
StreamAdaptImpl:TStreamAdapter;
begin
Stream:=TMemoryStream.Create;
StreamAdaptIntf:=TStreamAdapter.Create(Stream, soOwned);
StreamAdaptImpl:=TStreamAdapter(StreamAdaptIntf);
Assert(Integer(StreamAdaptImpl) <> Integer(StreamAdaptIntf));
end;
It's probably no help, this question was asked ages ago, but for anyone checking this out in future...
You've probably just posted test code but your interface should contain a GUID, the GUID uniquely defines the interface to the compiler (Ctrl+Shift+G in Delphi 2009).
IDummy<T> = interface
['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
procedure DoSomething;
end;
Hard casting is generally unsafe. A hard cast is only really acceptable where you know that an objects type will be correct. It's preferable when casting to check its type before the cast as follows...
var
lTypeA: TTypeA;
begin
if ObjectA is TTypeA then begin
lTypeA := TTypeA(ObjectA);
end;
Even better I'd perform an "as" cast, which I think will cause an exception if it is invalid. This REALLY is preferable! I've written code that performs a hardcast only to spend hours and hours figuring out that actually my cast was wrong... Delphi won't tell you if you cast to the wrong type, then when you later use the object you end up in a whole debugging nightmare. The as will raise an exception and guide you to the problem in your code
var
lTypeA: TTypeA;
begin
if ObjectA is TTypeA then begin
// Will raise an exception if ObjectA is not TTypeA,
// in this simple case the ObjectA is TTypeA test is redundant
lTypeA := ObjectA as TTypeA;
end;
I'd really only cast between objects in delphi. Delphi has a helpful function "supports" which will determine whether an object implements an interface and give you back an instance of that interface. You can then use the local variable returned to perform whatever function you need from the interface
if Supports(ImplBroken, IThisBreaksIt, lObjInterface) then
begin
lObjInterface.DoSomething;
end;
Code in full...
type
// Generic IDummy interface
IDummy<T> = interface
['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
procedure DoSomething;
end;
// Don't alias interfaces if possible
// This is a more specific version of your interface
IThisBreaksIt = interface(IDummy<Byte>)
['{76EFA371-4674-4190-8A4B-06850103C1D8}']
end;
TMyRecord = record
// I would suggest, if you can avoid it don't store interfaces
// in a record, just simple types - just my opinion, delphi doesn't stop you
Intf: IDummy<Byte>;
end;
// Remember this is interfaced, so the object only exists while refcount > 0
TDummy = class(TInterfacedObject, IDummy<Byte>)
protected
{ IDummy<T> }
// interface methods should be protected
// So ideally we refer to the interface of this object,
// not publicly available methods
procedure DoSomething;
public
end;
var
ARecord: TMyRecord;
Item: IDummy<Byte>; // Think this should be IThisBreaksIt
ImplWorks:TDummy;
ImplBroken: IThisBreaksIt;
lObjInterface: IThisBreaksIt;
begin
ARecord.Intf := TDummy.Create;
Item := ARecord.Intf;
// Nasty hard cast here - what if your interfaced object is destroyed
// before you reach this code section?
ImplWorks := TDummy(Item);
// This line compiles, buts it's really not the right way to get the interface
ImplBroken := IThisBreaksIt(ARecord.Intf);
// I believe this is the right way to get the interface for an object
if Supports(ARecord.Intf, IThisBreaksIt, lObjInterface) then
begin
lObjInterface.DoSomething;
end;
end;

Can I use generics to do the same operation on similar types of controls?

I am using Delphi 2010 and I have a unit where over the years I have added my own procedures and functions that can be used with any project I make, such as:
function ListBoxIsSelected(ListBox: TListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
The above uses TListBox as a parameter, so whenever the above function is used I must supply a listbox that is of TListBox class.
Now suppose I have some other component libraries that could work with the same function, For example the Jedi component classes.
How could I use the above function, when the Jedi listbox is TJvListBox class and my function is looking for TListBox class? Although both components are practically the same, the class names are different. If I provided the same function specifically for the TJvListBox it would likely work because they are both "listboxes":
function ListBoxIsSelected(ListBox: TJvListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
Now, I have whole load of procedures and functions written in the same kind of way where I need to pass a component as a parameter. Having to rewrite them again just to work with a different component class is not feasible!
How can I write this with generics?
You can't write that with generics, unless your target classes all descend from the same base class of course. (But then you wouldn't need generics for it.)
If you really want something that can check if the ItemIndex property on any object <> -1, though, you can do that with a different Delphi 2010 feature: extended RTTI.
uses
SysUtils, RTTI;
function IsSelected(item: TObject): boolean;
var
context: TRttiContext;
cls: TRttiType;
prop: TRttiProperty;
ItemIndex: integer;
begin
if item = nil then
raise Exception.Create('Item = nil');
context := TRttiContext.Create;
cls := context.GetType(item.ClassType);
prop := cls.GetProperty('ItemIndex');
if prop = nil then
raise Exception.Create('Item does not contain an ItemIndex property.');
ItemIndex := prop.GetValue(item).AsInteger;
result := ItemIndex <> -1;
end;
Careful, though. There's no compile-time type checking here, and this process is significantly slower than your original routine. You probably won't notice it, but if you call something like this in a tight loop, it will slow it down.
I don't understand how I can write this with Generics?
You can’t – not unless your component implements a common interface or inherits from a common base class with the standard ListBox, and that interface / base class offers the ItemIndex property.
In fact, this use-case isn’t such a great example of generics because using an interface or base class in the declaration would work just as well.
In this case, you can write two overloaded functions, one expecting TJvListBox and the other expecting TListBox.
In more complex cases this approach may not apply so well, but I think your case is simple enough for this solution.
I cannot look it up right now (on holiday, no Delphi), but don't TJvListBox and TListBox descend from a common ancestor (my guess would be: TCustomListBox)? In that case something like this should work:
interface
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
implementation
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := _ListBox.ItemIndex <> -1;
end;
Just in case ItemIndex (as I said: I cannot check right now) is protected in TCustomListBox, you can just use a typecast hack:
type
TListBoxHack = class(TCustomListBox)
end;
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := TListBoxHack(_ListBox).ItemIndex <> -1;
end;
(I just thought I should mention this since the original question has already been answered: Using Generics does not help here.)

Add a property on TWinControl Class

I want to add a published property into TWinControl.
Is there someway to do this without the necessity of recompiling the base source code ?
If not, some way to recompile the base source code without too much troubles ?
Tks in advice...
EDIT 'CAUSE OF NEW IDEAS
Alright, What I'm thinking to do I'm trying to override the _GetMem from System.pas for classes
inherited from TWinControl.
Why ? 'Cause I'll alloc some extra space to the objects enough to an integer.
Why an integer ? 'Cause this way I can add any pointer to object.
So on the helper class to TWinControl I can make a Get an Set function to access this space of memory.
Good isn't it ? How to do this ?
Overrideing the GetMem procedure I can use the same strategy used on FastCode, create a jumper to the new procedure.
What I need now is understand how this memory alloc works InstanceSize to override this.
At all I'm studding how do Delphi do this... And to add this on DFM I will do the same way, I'll create a jumper to the filer.
Someone have some idea to add the new space in objects ? What method I need to override ? The jumper I know how to do.
Tks Again.
EDIT = Evolution
I think that I did the injection of memory.
I need to do more tests.
I've just did it, I'm not caring about optimizations at the moment, if some one would like to test it, here goes the code.
Just add the unit as the first unit of your project.
unit uMemInjection;
interface
uses
Controls;
type
THelperWinControl = class Helper for TWinControl
private
function RfInstanceSize: Longint;
function GetInteger: Integer;
procedure SetInteger(const Value: Integer);
public
property RfInteger: Integer read GetInteger write SetInteger;
end;
implementation
uses
Windows;
procedure SInstanceSize;
asm
call TWinControl.InstanceSize
end;
function THelperWinControl.GetInteger: Integer;
begin
Result := Integer(PInteger(Integer(Self) + (Self.InstanceSize - SizeOf(Integer)))^);
end;
function THelperWinControl.RfInstanceSize: Longint;
begin
Result := PInteger(Integer(Self) + vmtInstanceSize)^;
Result := Result + SizeOf(Integer);
end;
/////////////////////////////////////////////// FastCode ///////////////////////////////////////////////
type
PJump = ^TJump;
TJump = packed record
OpCode: Byte;
Distance: Pointer;
end;
function FastcodeGetAddress(AStub: Pointer): Pointer;
begin
if PBYTE(AStub)^ = $E8 then
begin
Inc(Integer(AStub));
Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
end
else
Result := nil;
end;
procedure FastcodeAddressPatch(const ASource, ADestination: Pointer);
const
Size = SizeOf(TJump);
var
NewJump: PJump;
OldProtect: Cardinal;
begin
if VirtualProtect(ASource, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
NewJump := PJump(ASource);
NewJump.OpCode := $E9;
NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);
FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
VirtualProtect(ASource, Size, OldProtect, #OldProtect);
end;
end;
/////////////////////////////////////////////// FastCode ///////////////////////////////////////////////
{ THelperWinControl }
procedure THelperWinControl.SetInteger(const Value: Integer);
begin
PInteger(Integer(Self) + (Self.InstanceSize - SizeOf(Integer)))^ := Value;
end;
initialization
FastcodeAddressPatch(FastcodeGetAddress(#SInstanceSize), #TWinControl.RfInstanceSize);
end.
Thanks to Smasher, I remembered how the Delphi team used class helpers and a designer trick to add properties to Delphi 2007 without breaking binary compatibility with Delphi 2006.
See this great article by Hallvard Vassbotn on how to do this.
I think it solves most, if not all, of your problems.
look for these things in the article:
TCustomFormHelper = class helper for TCustomForm
The FPixelsPerInch storage hack
Injecting design-time properties
Defining the streaming properties
You'll have to work your own way to do the streaming, though, as you hook from the outside world into TWinControl, but that might be possible too.
--jeroen
Delphi2007 and higher have "class helpers".
You can introduce new functions and properties, but no fields/variables. So you have to store the value of you new property in a extra object (via factory or whatever) or (very ugly) in the .Tag property...
Don't know if class helper also work in packages/design time?
If you are using this property only on the application level, you may use the following approaches:
composition: bundle a reference to TWinControl object with other properties into new class, and pass/operate objects this class in your calls
dictionary-like functions: GetMyPropertyFor( AWinControl: TWinControl): and SetMyPropertyFor( AWinControl: TWinControl: AValue: ), which internally maintain additional property for each called TWinControl object
ADDITION: Based on your additional comment, existing Tag property should play well for your needs. You can even define 'levels' by using different values there.
No, there is no way to modify TWinControl without recompiling the VCL. Also I don't recommend changing the VCL (since having a "custom" VCL can impact the portability of your project - at the very least between Delphi installations). I would aim at making another class that inherit from TWinControl and then add your published property to this new class.
If you still want to change the VCL see the following post:
http://www.delphigroups.info/2/6/744173.html
Note that "you will no longer be able to compile using runtime
packages"...
(I know the answer is a bit dense, comment on it what details you need more info about)
What you could do is what for instance TGridPanel does: it adds the Column, Row, ColumnSpan and RowSpan 'properties' to the object inspector for all components that are on the GridPanel.
That will solve your design-time support.
I thought I had a reference on how the TGridPanel does this (and TFlowPanel does similar things), but I can't find it right now. Probably Ray Konopka explained this during a conference, but that info might not be on-line.
For run-time support, you could go with class helpers.
When using class helpers, note that only the nearest visible one for a class will apply.
Another route you might follow is to use the Tag property (which is an Integer, but you can cast it to a Pointer or a TObject), but you might be bitten by others using that too.
You'd have to create your own design-time support for those tag properties though.
--jeroen

Resources