Why Delphi XE3 gives "E2382 Cannot call constructors using instance variables"? - delphi

I have a simple piece of code, that compiles in Delphi XE2 but not in XE3, and I don't know why. I have reduced the problematic code to a small bit and would like to know what's wrong with it in Delphi's opinion. Trying to compile a project containing this unit in Delphi XE 2 works fine, but in Delphi XE3 (trial), it gives "[dcc32 Error] AffineTransform.pas(26): E2382 Cannot call constructors using instance variables". The only "eccentric" thing I know of here is the use of the old-school "object" type, where the constructor isn't really exactly the same thing as in real objects (TObject-based class instances).
If I replace the words 'constructor' in this object with 'procedure', then it compiles ok, but why is this, and is this an ok change to do in my code, i.e. is it a change that will have no effect on the functionality?
unit AffineTransform;
interface
type
{ Rectangular area. }
TCoordRect = object
public
Left, Top, Right, Bottom: Real;
constructor CreatePos(ALeft, ATop, ARight, ABottom: Real);
procedure Include(AX, AY: Real);
end;
implementation
constructor TCoordRect.CreatePos(ALeft, ATop, ARight, ABottom: Real);
begin
Left := ALeft;
Top := ATop;
Right := ARight;
Bottom := ABottom;
end;
procedure TCoordRect.Include(AX, AY: Real);
begin
CreatePos(AX, AY, AX, AY)
end;
end.

For this legacy Turbo Pascal style object, there is really no meaning to the keyword constructor. Although an object constructor does have some special treatment, there's absolutely no need for that here. What have here is nothing more than a record with some methods.
The XE3 compiler was changed so that it no longer allows you to call a constructor on Self inside an instance method. That is the case for both class and object. I've not seen any documentation of why this change was made. No doubt in time it will seep out.
Your immediate solution is to replace constructor with procedure. In the longer term, it would make sense to turn this into a record rather than an object.
I would also council you to change the name of the method to Initialize. Some library designers seem to opt for using Create and Free methods on their records. This had led to immense amount of code being written like this:
ctx := TRttiContext.Create;
try
....
finally
ctx.Free;
end;
In fact all that code is spurious and can simply be removed! A TRttiContext variable will automatically initialize itself.
That sort of design also sets a giant Heffalump Trap for that faction of Delphi coders that like to use FreeAndNil. Passing a record to FreeAndNil leads to some interesting fireworks!

I have a simple piece of code, that compiles in Delphi XE2 but not in XE3, and I don't know why.
You are trying to call a constructor inside of a method of an instance that is already instantiated and initialiized. The compiler does not allow that anymore. More specifically, this code:
procedure TCoordRect.Include(AX, AY: Real);
begin
CreatePos(AX, AY, AX, AY)
end;
Is the same as this code:
procedure TCoordRect.Include(AX, AY: Real);
begin
Self.CreatePos(AX, AY, AX, AY)
end;
And you cannot call a constructor on the Self variable anymore. Why? IIRC, it has to do with compiler's ongoing shift to supporting mobile development.

Related

Trying to combining generics and operator overloading in Delphi [duplicate]

I'm new in Delphi. For a project required by my company, I need to translate some code from our existing C++ classes to Delphi. Some of these classes are templates, such as:
template <class T>
struct APoint
{
T m_X;
T m_Y;
virtual void Add(T value);
};
template <class T>
void APoint<T>::Add(T value)
{
m_X += value;
m_Y += value;
}
I use it e.g. with this code
APoint<float> pt;
pt.m_X = 2.0f;
pt.m_Y = 4.0f;
pt.Add(5.0f);
and this works well.
Now I need to write equivalent code for Delphi. I tried to write a Delphi Generic class, based on the C++ code above:
APoint<T> = record
m_X: T;
m_Y: T;
procedure Add(value: T);
end;
procedure APoint<T>.Add(value: T);
begin
m_X := m_X + value;
m_Y := m_Y + value;
end;
However this code does not compile. I get this error:
E2015 Operator not applicable to this operand type
AFAIK this code should work, and I don't understand what is wrong with it. So can anybody explain to me:
Why a such code does not compile in Delphi?
What is the correct (and simplest) way in Delphi to create a template class that provides an Add() function, as closest as possible to the C++ code and usage above?
EDITED on 17.10.2016
Thanks for all the replies. So if I understood correctly, there is no way to create a c++-like style template, because Delphi imposes several constraints that not exists in c++.
Based on that, I searched a workaround to reach the objective I want. I found the following solution:
IPoint<T> = interface
procedure Add(value: T);
end;
APoint<T> = class(TInterfacedObject, IPoint<T>)
m_X: T;
m_Y: T;
procedure Add(value: T); virtual; abstract;
end;
APointF = class(APoint<Single>)
destructor Destroy; override;
procedure Add(value: Single); reintroduce;
end;
destructor APointF.Destroy;
begin
inherited Destroy;
end;
procedure APointF.Add(value: Single);
begin
m_X := m_X + value;
m_Y := m_Y + value;
end;
I use it e.g. with this code
procedure AddPoint;
var
pt: IPoint<Single>;
begin
pt := APointF.Create;
APointF(pt).m_X := 2.0;
APointF(pt).m_Y := 4.0;
APointF(pt).Add(5.0);
end;
and this works well. However I find the style a little heavy, e.g. the necessity to use APointF(pt). So, in relation to code above, my questions are:
Is this solution a good solution? (i.e. better to write a version of each record for each type I want to support, like e.g APointF, APointI, APointD, ...)
Is there a way to simplify this code, e.g. a solution to call pt.m_X directly without the APointF(pt) conversion? (NOTE I omitted here the implementation of properties, even if I think them more elegant than accessing the variable directly)
What about the performances of this solution? (I.e. is this solution drastically slower than a direct m_X := m_X + value addition?)
Finally, I saw another solution in the Delphi code, where it is possible to implement an equality comparison of 2 generic types this way:
function APoint<T>.IsEqual(const other: APoint<T>): Boolean;
var
comparer: IEqualityComparer<T>;
begin
Result := (comparer.Equals(m_X, other.m_X) and comparer.Equals(m_Y, other.m_Y));
end;
I tried to read the code behind the scene, however I found it terribly complicated. So, my questions are:
Is a such solution better than the one above proposed?
Is there a similar ready-to-use solution for mathematical operations?
Are the performance of a such solution acceptable?
Thanks in advance for your replies
Regards
Delphi Generics are intrinsically different from C++ template, and resemble more their C# counterpart.
In C++ you can do any operation on template types, and at time of the template instantiation the compiler checks that the operation you are performing in the template are available for the specific type you are using. If not you get a compiler error.
In Delphi (and many other languages) you declare a generic type possibly providing some declarative constraints, and those constraints -- base classes or interface -- determine the operations you can do on the generic type. At instantiation time, the only check is if the declared type fits the constraint.
Arguably, the Delphi language could add constraints for floating point or ordinal types, but this would provide a very limited flexibility (changing the floating or integer type you can use in the generic instance). I personally don't regard this as a critical feature.
Delphi generics do not support arithmetic operators that act on generic types. In order for the compiler to accept the code it needs to know that each operation on a generic type is going to be available upon instantiation.
Generic constraints allow you to tell the compiler what capabilities the type has. However generic constraints do not allow you to tell the compiler that the type supports arithmetjc operators.
Unfortunately what you are trying to do is simply not possible. For sure you can construct frameworks yourself that can use tools like interfaces to get the arithmetic performed but doing so gives up performance. If that is acceptable then fine. Otherwise you are best biting the bullet and avoiding generics here.
Oh for C++ templates.
Pointing this out for anyone else looking for an Object Pascal solution to this general issue:
Note that Free Pascal quite simply does support exactly what they're trying to do here (even in "Delphi-syntax compatibility" mode.)
As someone who had only used Free Pascal for a long time, I was honestly very surprised that Delphi doesn't allow this at all, once I realized that was the case. It's a significant limitation IMO.
Valid Free Pascal code:
program Example;
// Using Delphi-mode here allows us to declare and use
// generics with the same syntax as Delphi, as opposed to
// needing the "generic" and "specialize" keywords that
// FPC normally requires.
{$mode Delphi}
// To allow +=, another nice FPC feature...
{$COperators On}
type
TGPoint<T> = record
X, Y: T;
// static class function instead of constructor here so we can inline it
class function Create(constref AX, AY: T): TGPoint<T>; static; inline;
// define our operator overload
class operator Add(constref Left, Right: TGPoint<T>): TGPoint<T>; inline;
end;
class function TGPoint<T>.Create(constref AX, AY: T): TGPoint<T>;
begin
with Result do begin
X := AX;
Y := AY;
end;
end;
class operator TGPoint<T>.Add(constref Left, Right: TGPoint<T>): TGPoint<T>;
begin
with Result do begin
X := Left.X + Right.X;
Y := Left.Y + Right.Y;
end;
end;
var SP: TGPoint<String>;
begin
SP := TGPoint<String>.Create('Hello, ', 'Hello, ');
SP += TGPoint<String>.Create('world!', 'world!');
with SP do begin
WriteLn(X);
WriteLn(Y);
end;
end.
The program of course prints:
Hello, world!
Hello, world!

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));

How to cast a Interface to a Object in Delphi

In delphi 2009 I have a reference to a IInterface which I want to cast to the underlying TObject
Using TObject(IInterface) obviously doesn't work in Delphi 2009 (it's supposed to work in Delphi 2010 though)
My searches lead me to a function that should do the trick, but it doesn't work for me, I get AV's when I try to call methods on the returned object.
I can't really modify the Classes and I know that this breaks OOP
Instead of relying on Delphi's internal object layout you could also have your objects implement another interface which would simply return the object. This, of course, only works if you have access to the source code of the objects to begin with, but you probably shouldn't even use these hacks if you don't have access the source code of the objects.
interface
type
IGetObject = interface
function GetObject: TObject;
end;
TSomeClass = class(TInterfacedObject, IGetObject)
public
function GetObject: TObject;
end;
implementation
function TSomeClass.GetObject: TObject;
begin
Result := Self;
end;
You are right. Beginning with Delphi 2010, you are able to use the as operator, e.g. via aObject := aInterface as TObject or even aObject := TObject(aInterface).
This as operator use a special hidden interface GUID (ObjCastGUID) to retrieve the object instance, calling an enhanced version of TObject.GetInterface, which does not exist prior to Delphi 2010. See source code of System.pas unit to see how it works.
I've published some code working for Delphi 6 up to XE2, including Delphi 2009.
See http://blog.synopse.info?post/2012/06/13/Retrieve-the-object-instance-from-an-interface
There is a nice alternative when you know the implementing object is a TComponent descendant.
You can use the IInterfaceComponentReference interface, which is defined up in Classes unit:
IInterfaceComponentReference = interface
['{E28B1858-EC86-4559-8FCD-6B4F824151ED}']
function GetComponent: TComponent;
end;
And then it's declared in TComponent (and implemented to return self):
TComponent = class(TPersistent, IInterface, IInterfaceComponentReference)
So if you know the implementing object is a TComponent then you can do this:
function InterfaceToComponent(const AInterface: IInterface): TComponent;
var
vReference: IInterfaceComponentReference;
begin
if Supports(AInterface, IInterfaceComponentReference, vReference) then
result := vReference.GetComponent
else
result := nil;
end;
In short: you shouldn't or add an interface with a method that returns the pointer for you. Anything else is hackery.
Note that an interface "instance" may be implemented in another language (they are COM compatible) and / or may be a stub for something out of process etc etc.
All in all: an interface instance only agrees to the interface and nothing else, certainly not being implemented as a Delphi TObject instance
Hallvard's hack is very specific to how the Delphi compiler generates code. That has been remarkably stable in the past, but it sounds like they changed something significant in Delphi 2009. I only have 2007 installed here, and in that, Hallvard's code works fine.
Does GetImplementingObject return NIL?
If so, then if you debug and set a break-point on the case statement in the GetImplementingObject routine, what does the value of QueryInterfaceThunk.AddInstruction evaluate to in the debugger?
var
N, F: NativeInt; // NativeInt is Integer(in delphi 32bit )
S: TObject;
begin
N := NativeInt(Args[0].AsInterface) - 12;
{subtract 12 byte to get object address(in x86 ,1 interface on class) }
S := TObject(N);
writeln(S.ToString);
end;

Accessing Sub functions /procedures from DPR or other function / procedure in Delphi

As much I know - Subroutines are with Private access mode to its parent unction / procedure, right?
Is there any way to access them from "outer-world" - dpr or other function / procedure in unit?
Also - which way takes more calcualtion and space to compiled file?
for example:
function blablabla(parameter : tparameter) : abcde;
procedure xyz(par_ : tpar_);
begin
// ...
end;
begin
// ...
end;
procedure albalbalb(param : tparam) : www;
begin
xyz(par_ : tpar_); // is there any way to make this function public / published to access it therefore enabling to call it this way?
end;
// all text is random.
// also, is there way to call it from DPR in this manner?
// in C++ this can be done by specifing access mode and/or using "Friend" class .. but in DELPHI?
Nested procedures/functions - those declared inside another procedure or function, are a special type, because they can access the stack (and thereby parameters/local variables) of the procedure they are nested in. Because of this, and Delphi scope rules, there is no way to access them outside the "parent" procedure. You use them only if you need to take advantage of their special features. AFAIK Delphi/Pascal is one of the few languages to have this feature. From a compiler point of view the call has some extra code to allow accessing the parent stack frame, IIRC.
AFAIK "friend" class/functions in C++ are different - they are class access methods, while in your example you are using plain procedures/functions.
In Delphi all procedure/classes declared in the same unit are automatically "friend", unless strict private declarations are used in latest Delphi releases. For example this code snippets will work, as long everything is in the same unit:
type
TExample = class
private
procedure HelloWorld;
public
...
end;
implementation
function DoSomething(AExample: TExample);
begin
// Calling a private method here works
AExample.HelloWordl;
end;
Note: Embedded Routines <> Private/Protected Methods.
Embedded routines i.e. routines inside routines can not be accessed by external routines.
You have posted an example of an Embedded routine, I also heard them called Internal Routines.
Here is another example:
procedure DoThis;
function DoThat : Boolean;
begin
// This Routine is embedded or internal routine.
end;
begin
// DoThat() can only be accessed from here no other place.
end;
Regardless of visibility, methods on classes, can be called using Delphi 2010 via RTTI. I have detailed how to do this in this article.
If you are in the same Unit methods on a class can be accessed by any other code regardless of visibility, unless they are marked with Strict Private. This Question has more details and good example code in the accepted answer.
If you are in two different units you can use the Protected Method Hack to access the protected methods. Which is detailed in detailed in this article.
Yes, you can access a subroutine, which is nested in other (parent) subroutine, from the outer world. Though it's somewhat tricky. I've found this howto in the web.
How to pass nested routine as a procedural parameter (32 bit)
Delphi normally does not support passing nested routines as procedural parameters:
// This code does not compile:
procedure testpass(p: tprocedure);
begin
p;
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(inner);
end;
The obvious workaround is to pass procedure address and typecast it within testpass:
// This code compiles and runs OK
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(#inner);
end;
There is, however, a pitfall in the above example - if the "inner" routine references any variable that was pushed onto the stack before the "inner" procedure was called from testpass (calltestpass parameters - if there were any, or local variables in calltestpass - if there were any), your system most probably crashes:
// This code compiles OK but generates runtime exception (could even be
// EMachineHangs :-) )
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
The reason is, in simple words, that the stack frame arrangement
was "broken" by the call to testpass routine and "inner" procedure
incorrectly calculates parameters and local variables location
(do not blame Delphi, please).
The workaround is to set up the correct stack context before
"inner" is called from within "testpass".
// This code compiles and runs OK
{$O-}
procedure testpass(p: pointer);
var callersBP: longint;
begin
asm // get caller's base pointer value at the very beginning
push dword ptr [ebp]
pop callersBP
end;
// here we can have some other OP code
asm // pushes caller's base pointer value onto stack and calls tProcedure(p)
push CallersBP
Call p
Pop CallersBP
end;
// here we can have some other OP code
end;
{$O+}
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(#inner);
end;
Please note the optimization is switched OFF for testpass routine - optimization generally does not handle mixed OP/assembler code very well.
No, there is no way to do what you're asking. The xyz function is callable only by the enclosing blablabla function. Outside that function, xyz is not in scope and there is no way to name it. If C++ allowed nested function, there wouldn't be any way to refer to it, either, just like there's no way to refer to functions with static linkage from outside the current translation unit.
If you need to call xyz from outside the blablabla function, then move xyz outside. If you need to call it from outside the current unit, then you need to declare that function in the unit's interface section. Then, add that unit to the external code's uses clause and you can call xyz from wherever you want, even the DPR file.
If xyz refers to variables or parameters of the blablabla function, then you'll need to pass them in as parameters since xyz will no longer have access to them otherwise.
The concept of access specifiers isn't really relevant here since we're not talking about classes. Units have interface and implementation sections, which aren't really the same as public and private sections of a class.

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