Turbocharging Delphi 2010: Adding dynamic functionality to 3rd party frameworks (Read: VCL)

giraffeYeah, I know: Use Tag. But no, I won’t. If I can. But sometimes I cannot. But let me explain the problem first:

Many times there is a need to have metadata which has at least one of the properties bellow…

  • dynamic
  • added at runtime
  • to objects which we don’t control their class specification

…then we are in trouble… Or use the plain ol’ Tag. Store in the Tag property (which is btw a ‘me too’ from Visual Basic) any form of coded information. Many of you put there even an object pointer. But did you thought what will happen if this pointer goes to 64-bit and the Tag’s size stays the same? …or any other thing which will break your assumptions, of course. Because using this technique you made several. In fact you break the TObject’s abstraction.

Or, of course, another approach is to use a parallel data structure (for example dictionaries in D2009 and especially in D2010) to keep this metadata – but, yes, syncing the lifetime of these two entities (the main object and the metadata) can be a royal pain. Also the object cannot ‘ask’ even using the reflection, what metadata is attached to it.

Another approach is by using attributes in Delphi 2010. Very nice indeed, but for this you must control the class specification. Also, you cannot add code.

A better ‘workaround’ one can see in some libraries which add a TagString: string; property. Well, that’s definitely better but, of course, their implementation is limited to their library. No TObject.TagString – no fun.

A yet (somewhat) better one is the TagObject: TObject – but again this isn’t added to the TObject so you’re limited to that library which uses it.

A much better one is class helpers.

As you perhaps know, the class helpers extend the functionality of the class which we want to alter, by adding “some methods” to it. But the class helpers can do more than this: from Delphi 2009 onward (IIRC) – sure in Delphi 2010 they support inheritance and overriding virtual methods between them. Hence the thing bellow will compile and work:


TPanelHelper = class helper for TPanel
public
  procedure ChangeCaption; virtual;
end;

TPanelHelper2 = class helper (TPanelHelper) for TPanel
public
  procedure ChangeCaption; override; //yes!
  procedure ChangeColor; //just another method shown as an example
end;

implementation

procedure TPanelHelper.ChangeCaption;
begin
  Caption:='['+Caption+']';
end;

procedure TPanelHelper2.ChangeCaption;
begin
  inherited ChangeCaption; //the TPanelHelper's method
//  inherited; - won't work.
  Caption:='('+Caption+')';
end;

procedure TPanelHelper2.ChangeColor;
begin
  ParentBackground:=False;

  if Color=clGreen then
    Color:=clBtnFace
  else
    Color:=clGreen;
end;

So this in this way you can add dynamic functionality in the form of code. Of course, bear in mind that even if you can define and associate multiple class helpers with a single class type, only zero or one class helper applies in any specific location in source code. The class helper defined in the nearest scope will apply. Class helper scope is determined in the normal Delphi fashion (i.e. right to left in the unit’s uses clause). (snipped from Delphi’s help – thanks guys).

But we must note that we cannot override the virtual methods of the helped class.
And here is a big discussion: is good to allow the developer to inject new functionality in there? Or perhaps it looks dangerous and a mechanism similar with the events to sandbox the changes would be more appropriate? What are your opinions on this?

…returning to our main theme (adding functionality), in Delphi 2010, you can have class constructors and class destructors also in the Class Helpers which is a great feature for doing a bunch of things. For example…

Simulating properties.

Ok, we cannot have fields in the Class Helpers. And this is bad. But we can simulate them. And this is good. Even if we cannot have instance fields we have class fields and we can use such a class var to keep our metadata. And of course we use the class constructors and destructors to manage the life of our dictionary:


TPanelHelper2 = class helper for TPanel
public
  class var
    MetaData: TDictionary<TPanel, string>; //store some strings - an example
  procedure PutData(aData: string);
  function ReadData: string;
  class constructor SetUp;
  class destructor TearDown;
end;

implementation

procedure TPanelHelper2.PutData(aData: string);
begin
  MetaData.AddOrSetValue(Self, aData);
end;

function TPanelHelper2.ReadData: string;
begin
  if MetaData.ContainsKey(Self) then
    Result:=MetaData.Items[Self]
  else
    Result:=''; //or whatever
end;

class constructor TPanelHelper2.SetUp;
begin
  MetaData:=TDictionary<TPanel, string>.Create;
end;

class destructor TPanelHelper2.TearDown;
begin
  MetaData.Free;
end;

//Unfortunately we cannot override a virtual member in the Helped Class. Sigh!
//procedure TPanelHelper2.FreeInstance;
//begin
//  if MetaData.ContainsKey(Self) then
//    MetaData.Remove(Self);
//
//  inherited;
//end;

As you see we can do quite a lot with the class helpers. But I think that there are some improvements to make with regard to our theme:

  • A compiler warning showing which class helper is actually used. Something like “Son, you use TFooHelper5 from Unit3 for the class TFoo” – Wording subject of change, of course. Also, another thing to consider is to allow many class helpers to attach to the class, bringing them more closer to a class fragments / partial class behavior.
  • Allow to override the virtual / dynamic members of the helped class. (hmm… hmmmm…. mmmm….)
  • Remove the “[DCC Error] Unit4.pas(75): E2169 Field definition not allowed after methods or properties” limitation – of course this will help also the Class Helpers, no? (if we believe the compiler, then this is the limitation which keeps us away for having fields in class helpers)
  • As a complementary feature, explore the possibility to add a Property TObject.ObjectList: TObjectList; which will encapsulate a somewhat similar mechanism. If someone wants to, please let me know and I’ll expand the topic. The downside here is that you must hard cast every time the members of the list to the appropriate type.

…have other ideas for this?

    19 thoughts on “Turbocharging Delphi 2010: Adding dynamic functionality to 3rd party frameworks (Read: VCL)

    1. Pingback: Asking the impossible « TURBU Tech

    2. Replacing virtual methods is a dangerous idea. Though IIRC, JCL has some stuff to subsitute any virtual method with yours – just a hack. Why not just derive your subclass? Or the virtuals you need to override are not public or protected?
      IMHO, true AOP would be better. From my experience in most cases it’s enough to be informed about a method call then to change its internal behaviour.

      • You’re generally right. But there are cases in which one should work with a specified class (that’s why, in fact class helpers appeared). But nevermind, see my new post. About AOP – yes, this is a very big matter – I will dedicate another post for this. Till then, what are in your opinion, the advantages and disadvantages of AOP?

        • IMHO, the main advantage of AOP is that you leave the library you use as is. You don’t change the way the objects in this library behaves. Usually you have no need to change their internal algorithms. The main thing you generally need is to change the INTERACTION of the library with your system. And AOP is just about it – you can intercept any call, property setting, etc. So your system becomes a mediator between objects in a library – that’s it. Without AOP you would like to override some methods at the top of the hierarchy to provide logging, transactions etc.
          In other words, think you have a graph of linked objects. They behave and interact with their default rules. AOP could allow to plug into each and every graph branch and change or at least monitor the mesages passed around without modifying internal object’s logic. Here I presume that the library is designed so that it protects what is really sould be protected and internal objects logic can survive such a interaction intervention.

          • “IMHO, the main advantage of AOP is that you leave the library you use as is. “

            Yes, sure. But you must remember to call the library’s code explicitly. Eg. in Prism you must call Aspects.OriginalBody. Otherwise an aspect can change a behavior of a class.

            And AOP is just about it – you can intercept any call, property setting, etc.

            Yes, this is the gist of the AOP. And its advantage. Using a single move you can do a good thing with a great coverage. But also you can do a bad thing.

        • And the main disadvantage is that neither AOP nor other methods couldn’t replace an access to the library source code where you can change everything. If you know what are you doing of course πŸ˜‰

    3. Hello,

      after I read your idea here I have an other that use normal inheritance so all the problems you describe are gone. I have tested it with Delphi 2006 but I think it should work in every Delphi versions. The base idea is to replace a type like TLabel from StdCtrls with an other TLabel defined by myself. The Designer only knows StdCtrls.TLabel but the compiler see only my own definition of TLabel and create the desired instance. Have a look at the source:

      unit Unit1;

      interface

      uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;

      type
      TLabel = class(StdCtrls.TLabel)
      private
      FLines: TStrings;
      protected
      function GetLabelText: string; override;
      procedure LinesChanged(Sender: TObject);
      public
      constructor Create(AComponent: TComponent); override;
      destructor Destroy; override;
      property Lines: TStrings read FLines write FLines;
      end;

      TForm1 = class(TForm)
      Label1: TLabel;
      procedure FormCreate(Sender: TObject);
      private
      { Private-Deklarationen }
      public
      { Public-Deklarationen }
      end;

      var
      Form1: TForm1;

      implementation

      {$R *.dfm}

      { TLabel }

      constructor TLabel.Create(AComponent: TComponent);
      begin
      inherited;
      FLines := TStringList.Create;
      TStringList(FLines).OnChange := LinesChanged;
      end;

      destructor TLabel.Destroy;
      begin
      FLines.Free;
      FLines := nil;
      inherited;
      end;

      function TLabel.GetLabelText: string;
      begin
      Result := FLines.Text;
      end;

      procedure TLabel.LinesChanged(Sender: TObject);
      begin
      AdjustBounds;
      end;

      procedure TForm1.FormCreate(Sender: TObject);
      begin
      Label1.Lines.Add(‘First line’);
      Label1.Lines.Add(‘Last line’);
      end;

      end.

    4. Pingback: Turbocharging Delphi 2010 #2: Adding dynamic functionality to 3rd parties – the solution « Wings of Wind Software

    5. If “Tag” is a “Me Too” from Visual BASIC then “Partial classes” are a “me too” from C#, where they were only needed because the decision to realise the “form design” as IDE maintained code inside the class was a complete turd and they needed some way to move that crap “outside” of the class for maintenance/readability purposes whilst keeping it “inside” the class for compilation purposes.

      Partial classes are a maintenance nightmare waiting to happen and anyone seriously suggesting that they have a place in serious development needs to sit down and have a word with themselves.

      Class helpers are a stink idea too, not least because the implementation is severely limited and absolutely NOT designed for the purposes to which you suggest they are put.

      With the current implementation you can break other peoples code by using a class helper, and you can break your own code by using other peoples code that use them.

      Could that implementation be changed? Of course, but there really is no need and class helpers are functioning 100% according to intent and need. AS ORIGINALLY SPECIFIED. They were *never* intended for general purpose use.

      For one thing, *EVERYTHING* that you can achieve using the syntactic sugar of a class helper you can ALREADY achieve (and MORE) using the “long hand” pseudo-derived class without hiding the fact that you are breaking the designed extension points of the classes that you extend in this way.

      Because that is the only *CORRECT* way to extend a framework class…. using the extension points that the framework class itself provides.

      *ANYTHING* else, no matter how elegant or “clean” you might consider the syntactic sugar to be, is an extension for which the class was simply not designed and you cannot really know whether what you are doing is safe or reliable.

      • For one thing, *EVERYTHING* that you can achieve using the syntactic sugar of a class helper you can ALREADY achieve (and MORE) using the β€œlong hand” pseudo-derived class

        …almost everything. Most of what you can do with a class helper you can do with a standalone procedure that takes the object as an argument. But class helpers can access protected members, which external code can’t. A real derived class could do that, but it can’t apply the new methods you create to existing derived classes. AFAIK the only way to do both, without editing the code of the base class, is with a class helper.

      • “With the current implementation you can break other peoples code by using a class helper, and you can break your own code by using other peoples code that use them.”
        Since in the current implementation the Class Helpers doesn’t override / replace anything, it doesn’t break any code.

        “They were *never* intended for general purpose use.”
        Huh? Perhaps you’re taking as good what the Delphi team says, but others have a quite different opinion. And they have the market. Is one of the few (if not the only one) “Me Too” features which C# ‘borrowed’ from Delphi.

        Because that is the only *CORRECT* way to extend a framework class…. using the extension points that the framework class itself provides.

        Really? Then what’s this? πŸ™‚ It is easy to speak about others, isn’t it? πŸ˜‰

    6. Maybe a bit of a shameless (albeit indirect) self-plug, but this article by Hallvard Vassbotn describes a method I had devised a few years back, to add class-specific data to any class via VMT hacking. Using that technique, it’s quite easy to extend each instance of given class with additional properties (for example using a dictionary and another class-helper to address it).

      Yet another route that I haven’t investigated yet is piggy-backing into the new (D2009+) TObject.Monitor member. This is actually a linked list, so it’s possible to insert your own data into this list, and access that via a helper. (BTW – I still feel quite irritated about the way this ‘feature’ was imposed upon us, but that’s another subject altogether.)

    7. Please, add a “print” functionality to your articles. I cannot open my laptop on the bus, train or flight….

      πŸ™‚

      Thanks..

    Leave a reply to Maxim Cancel reply