Jump to content
microtronx

Runtime create new "fields" with RTTI on a tComponent

Recommended Posts

Hi, which is the simpliest way to add new properties to a existing component in runtime?

 

Lets say we have a tDataset and I want to att a fList:tList to it in runtime, if it does not exists. Is this possible?

  • Confused 1

Share this post


Link to post

Not possible, as François wrote, and also not useful.

What would be the purpose of adding new properties at runtime? How to you access them?

 

  • If what you expect is to make public something private, then you can trick the class by inheriting it locally, publishing the properties, then hard-casting to your type.
  • If what you expect is to add some fields to an existing class, it is not possible because the class instance size if hardcoded during compilation.
  • If what you expect is to have some additional information linked to an existing class, maintain a dictionary/map with the instances, and create a co-object associated with the main object lifetime, in which you put the needed data.
  • If what you expect is to have some additional information linked to an existing class (as a variation to the previous item), inherit from this class or even better nest this class into a main owner class/TComponenet, which will have your expected behavior.
Edited by Arnaud Bouchez

Share this post


Link to post
7 hours ago, microtronx said:

Hi, which is the simpliest way to add new properties to a existing component in runtime?

That is not possible.  Delphi is a compiled language, you can't alter object layouts at runtime.  The best you could do is store your data in another class/record object that you create at runtime, and then use the component's Tag property to point at that object.

Share this post


Link to post
1 hour ago, Remy Lebeau said:

That is not possible.  Delphi is a compiled language, you can't alter object layouts at runtime.  The best you could do is store your data in another class/record object that you create at runtime, and then use the component's Tag property to point at that object.

Please don't misuse "tag" for pointers.

 

I'd rather use a tDictionary<tcomponent, tSomethingelse> to store associations between components and objects. It's much more universal and transparent.

 

  • Like 2
  • Thanks 1

Share this post


Link to post
10 hours ago, A.M. Hoornweg said:

Please don't misuse "tag" for pointers.

 

I'd rather use a tDictionary<tcomponent, tSomethingelse> to store associations between components and objects. It's much more universal and transparent.

 

I have created a global "tDictionary<tComponent, tmxEvents>" and a small helper function to return an entry or add an entry if it does not exists ... working perfect. Thanks for your tips guys.

Share this post


Link to post
23 hours ago, A.M. Hoornweg said:

Please don't misuse "tag" for pointers.

There is nothing wrong with using the Tag property for pointers.  It is specifically intended to hold user-defined data, and intentionally pointer-sized because storing pointers in it is very common practice.  It is even documented that way 

Quote

Tag has no predefined meaning. The Tag property can store any additional integer value for the convenience of developers. Often, Tag stores a pointer. A Tag value can be typecast to the appropriate pointer type. Notice that on 64-bit platforms, all pointer types are 8 bytes in size, while on 32-bit platforms, pointer types are 4 bytes. These pointer sizes correspond to sizes of NativeInt integral values on 64-bit and 32-bit platforms.

So, there is no misuse here.

23 hours ago, A.M. Hoornweg said:

I'd rather use a tDictionary<tcomponent, tSomethingelse> to store associations between components and objects.

That is your choice, and it will certainly work.  I prefer to use the Tag instead, it offers a 1:1 relationship without any overhead of a lookup table.

 

Edited by Remy Lebeau

Share this post


Link to post

"tag" can fit a simple pointer-sized reference, sure, but you still need to handle ownership of the object. Also, "tag" has no way of knowing if the object it points to is still valid, you may need to clear the tag if the object is freed. That means writing boilerplate code.

 

 

TDictionary and tObjectdictionary are "better" because they can handle object ownership. TDictionary<T> can contain managed objects such as strings, interfaces and (I suspect) even anonymous methods. If tDictionary manages the lifetime of the objects, there can't possibly be an invalid association and it reduces boilerplate code.

 

 

Share this post


Link to post
6 hours ago, A.M. Hoornweg said:

"tag" can fit a simple pointer-sized reference, sure, but you still need to handle ownership of the object. Also, "tag" has no way of knowing if the object it points to is still valid, you may need to clear the tag if the object is freed. That means writing boilerplate code.

All of that applies to T(Object)Dictionary, as well.  If the TComponent or associated object is freed, you have to manually remove them from the dictionary to avoid dangling pointers.

6 hours ago, A.M. Hoornweg said:

TDictionary and tObjectdictionary are "better" because they can handle object ownership.

TDictionary doesn't support object management at all.  TObjectDictionary, on the other hand, simply lets you specify whether the dictionary owns the objects stored as Keys/Values, in that if an entry is removed/altered in the dictionary, the associate Key/Value objects are freed automatically as needed.  But, if those same objects are freed external to the dictionary, the dictionary entry that refers to them is not removed automatically.  You still have to handle that manually.

6 hours ago, A.M. Hoornweg said:

If tDictionary manages the lifetime of the objects, there can't possibly be an invalid association

The likelihood is reduced, but not eliminated.  It is still possible to have invalid references, if you are not careful.

6 hours ago, A.M. Hoornweg said:

and it reduces boilerplate code.

In your code, perhaps, but not the overhead needed to manage the dictionary elements, hash tables, etc.  You are actually adding runtime overhead to the program just to gain writing simpler code.

 

But whatever.  It is up to each user to decide whatever they are comfortable using.

Share this post


Link to post

tDictionary<T> does manage the lifetime of refcounted objects (interfaces, strings, anonymous methods, ...). If it's in the dictionary, then that's proof that the object is still alive. Try achieving that with "tcomponent.tag!"... 


tObjectlist<T> can manage the lifetime of plain vanilla tObject and the user can specify whether he his wishes this behavior or not in the constructor.  If yes, no dangling pointers, because the user is not supposed to free manually.  Of course the user is not protected from doing silly stuff like still freeing the objects manually. That's life.

 

Yes dictionaries add a few dozen KB to the executable.  But hey, RTTI adds a megabyte or so of metadata and for many/most of us it's only dead weight. If there's one single place in the compiler chain where we should be given more control, it's there.


Anyway, I myself have stopped using Tag for pointers to objects because dictionaries made my life much easier.  I find myself often using strings as a key because I totally like it when the code tells what it's doing.  Tag will only store a fixed pointer, but a dictionary will let me query if an object exists and where it is stored, without any ugly typecasting,  in a compact and legible way.  🙂

procedure tform1.Button1click(sender:tobject);
var callback:tReportShowEvent;
begin
  if ReportDict.TryGet('CurrentReport',callback) 
      then Callback();
end;

 

Share this post


Link to post

The other issue I have with .Tag is that once things get complex, it is hard to know who is (ab)using the Tag property for what. You have a brilliant idea to use the .Tag of some object to associate it with another object only to find out 3 weeks and 20 hard to reproduce crashes later that someone else is also using the .Tag under certain rare circumstances for other associations. Having explicit lists to hold different types of associations eliminates those problems. If you get in the habit of using .Tag all the time, you cannot even do a search for .Tag to see who is using the .Tag of your object because the search hits so many times. We used to use .Tag quite a bit but moved away from it primarily for that reason.

 

If its a class that we control, we will make a property just for the association, that is appropriately named. If it is a class from a 3rd party library we make a list like @A.M. Hoornweg suggested. Of course, that is just our preference based on our personal experience. Your mileage may vary.

Share this post


Link to post

You all lost me here... I added some properties (public, not published) to TkbmMemTable, TIBOQuery and more.

As long as it is the ancestor of everything else you can use interceptors / interposers. Google "Delphi interposer"

Or, if you do not mind all-runtime you can simply inherit and create the objects runtime for your own flavour, lDS := TMyFDMemData.Create;

But, no, if you want to add a property to TDataset it will not be added to, for example TkbmMemTable or TFDMemTable (or whatitiscalled) or any other classes inheriting from TDataset.

There's no way to "intercept" even using by-the-book OOP.

The TDictionary/TList approach is not bad at all, i use it a lot.

Tags make me a bit nervous 🙂

 

Also, depending on what you are trying to achieve there are some global instance variables in Data.DB that you can reassign at application startup. For example to speed up lookups.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×