Extending TObject with data at runtime

In a recent comment on this blog Patrick van Logchem suggested a way of extending an existing object instance with custom data at run-time (originally the idea of Thorsten Engler). The main idea is to be able to “assign” to an arbitrary object (whose sources you cannot change) some other object at run-time. This may prove to be useful in different scenarios when you need some additional data to be carried by an object.

The suggested idea was to use the monitor field which all TObject instances have. If you are not familiar with monitors in Delphi, let me explain: All TObject instances carry a new “hidden” field which is a pointer to a TMonitor (see System unit) structure. This referenced TMonitor structure is actually a kind of synchronization object that lets you solve some common threading tasks easier. You can read more about this in other posts, since this post is about using that field to store data.

The restrictions:

  • The solution must not be too simplistic. Simply rewriting the pointer in TObject instances is not allowed.
  • Normal monitor routines must function as they did before. This means that we must store a real monitor there alongside with the data.
  • No custom functions to simulate monitor support, and no class helpers. See previous item.
  • The attached data must be disposed when the object is disposed so to avoid memory and resource leaks.

The features:

  • Possibility to extend an object with another object: ExtendObject(Object, Extension);
  • Possibility to query an object for an extension: GetObjectExtension(Object): Extension;
  • Possibility to remove an extension from an object: RemoveObjectExtension(Object): Extension;
  • Object is any type of object in Delphi! No restrictions, no common ancestor; just plain TObject!
  • Extension is also simply a TObject value. It’s user-defined in it’s implementation and purpose.

The first implementation I came up with is non-intrusive. I wanted to avoid patching the System exposed functions at run-time. The following list enumerates the design and restrictions of the first implementation:

  • The unit must me USED directly after the inclusion of SysUtils in the main source file. This is an inconvenience of course, but it is a required one. Note that the unit must be included AFTER and not BEFORE SysUtils.
  • The unit overrides the values in the System.MonitorSupport variable and inserts it’s own custom routines used to obtain and release synchronization objects.
  • The unit uses the old System.MonitorSupport routines to do the real job. These routines are normally provided by the SysUtils unit — thus the dependency on SysUtils.
  • For each synchronization object requested, my custom routines return a fake handle which is actually a pointer to a structure containing a real handle and a TObject value.
  • This method does not use the monitor field per se; rather, it uses a field in the monitor itself.
  • Class helpers are used in implementation section to obtain access to internal method in the TMonitor structure.
  • While this method is non-intrusive at assembly level, it is surely more complex and uses more CPU cycles.

The second implementation is intrusive! It patches some functions in System unit so that my handler are executed in certain scenarios. The following list enumerates what’s going on in this one:

  • The unit should be USED after or before SysUtils. This restriction comes from the fact that monitors initialized before this unit is included have a different format. So there may be (or maybe not) problems.
  • Two functions are patched in System unit: TMonitor.Destroy(TObject) and TMonitor.Create(). First one is executed when a monitor is destroyed – normally at object death; and the second one is called when a monitor value is needed for the first time.
  • The two injected functions do basically the same thing as the System versions, with a slight turn – a TMonitor + Pointer value is created/destroyed. This bonus Pointer value hold the extension object.
  • Class helpers are used internally to gain access to some monitor support routines.
  • This method does not incur any overhead on normal monitor operations, so it is the preferred one.

And now to some code:

uses
  SysUtils,
  ObjectExtensions_Intrusive;

type
  TStrExtenstion = class
  end;

  TIntExtension = class
  end;

procedure DoStuff(const A: TObject);
var
  Extension: TObject;
begin
  if A = nil then
    Exit;

  Extension := GetObjectExtension(A);

  if Extension = nil then
    Exit;

  WriteLn(Extension.ClassName);
end;

var
  a, b, c: TObject;
begin
  a := TObject.Create;
  b := TObject.Create;
  c := TObject.Create;

  ExtendObject(a, TStrExtenstion.Create);
  ExtendObject(b, TIntExtension.Create);

  DoStuff(a);
  DoStuff(b);
  DoStuff(c);

  ReadLn;
end.

It’s not hard to imagine that this method can be used is different circumstances – the ideas are all yours!

MEGA WARNING: The attached code is barely tested, possibly unstable and even worse – maybe destructive. This is just a fun and proof of concept code and not something that can be used in applications.

The code can be found in this archive: [download#41]
Mentioned the authors of the idea.

You May Also Like

About the Author: Alexandru Ciobanu

9 Comments

  1. Por el comentario, creo deducir que el código fuente y la idea no coresponden al autor dela entrada. Pido excusas si es así pero no he encontrado referencias en la lectura que hice de la misma. Ahora he leido el comentario mas abajo citado y bien, queda mi rectificación. 🙂

    Ok.

    Un saludo.

    //Thorsten Engler propsed a (IMHO) small modification to this construct (he coined //the word ‘ObjectExtension’), which would make it possible that any data could //be added to TObject (even at runtime). This would open up the way to let //‘aspects’ add data to any class too (!)

  2. @Patrick: Sorry, I actually was going to mention your name but I decided to not include it since maybe you would not want that (privacy?). Anyway, I have updated the post and specified your name and the original author’s name.

    Thanks!

  3. Or you could forego all this trouble by simply storing your object and its associated data in a TDictionary (or TBucketList descendant). A class helper can make it appear to be a property on TObject.

    Actually, given that TObject with extra data is almost always an indication that you really need a more specialised thing, just do the TDictionary and class helper for that.

  4. 1. No class helpers – that is a must in a lot of circumstances.
    2. Dictionary doesn’t cut it either since you need to destroy the attached object when the object dies. In this case you must handle it manually and that is a very big disadvantage.
    3. You must use TObject and only it. Since in many cases you cannot change the original code.

    In these circumstances there is no other alternative.

  5. Alex:
    OK, a little out of sequence here, but here we go:
    2. TDictionary can actually free the object if you need. And if you derive from TBucketList you could do it too. No sweat. This is a fantastic solution is a far larger number of likely circumstances because the memory and performance penalty is almost neglible. In one example I have, items can be associated with one another in a number of possible ways. I do this with a couple of dictionaries so you can find objects by key or association (parent, child, whatever).

    Your responses in 1 and 3: Your disagreement basically stems from the case you discuss, namely associate data with ANY object, be it a button, dataset field component, business object, object to calculate control layout for a form and so on. My point in paragraph 2 was actually that this is a fairly esoteric case – it may be applicable in one case, but in the vast majority you’ll want to associate a given class of object with another. Like a business object with a list item for example. In instances where the one object cannot be changed by you (say the list item I mentioned) my solution works like a treat. The class helper may cause trouble if you use many of them, but for the general case it is going to be just fine. Your’s is a neat solution for the esoteric case and a cool technical achievement, but it is not so widely applicable.

  6. Oh, and your’s could be a simple way of adding aspect-like functionality, which my solution obviously doesn’t cover.

  7. Yep, it is a hack mostly – a proof of concept. I highly doubt anyone is going to use such an approach in production code.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.