Thursday, March 22, 2007

Hack#14: Changing the class of an object at run-time

Sometimes (such as when you for one reason or another need to stay backwards compatible with binary dcus) you may have to employ a hack or two. One such hack is to change the actual run-time class of an object instance. You might need to do this to override a virtual, dynamic or message method, for instance.

Vista and ProgressBar flickering

One such case; for some reason (most probably a bug fix) Microsoft changed the behavior of the ProgressBar control in Windows Vista so that it now sends a WM_ERASEBKGND message every time the progress changes and it needs to redraw itself. The TProgressBar component that wraps the native control does not handle WM_ERASEBKGND - nether as an explicit message override or in the general WndProc method (in fact it doesn't override WndProc). The net result is that each time the progress bar's Position value changes noticeable flicker can be observed as the control first erases the background completely and then draws the progress bar in the correct style. Jordan Russell reported this issue in Quality Central #38178.

Since Delphi 2007 was a release to target Windows Vista issues like this, it would have been simple to add a WM_ERASEBKGND override that skips the default TWinControl logic of FillRect'ing the client area.

type
TProgressBar = class(TWinControl)
private
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd);
message WM_ERASEBKGND;
end;

procedure TProgressBar.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
DefaultHandler(Message);
end;

The trouble is of course that Delphi 2007 was to be non-breaking release, so no interface section changes of existing classes was possible. But there are at least three different ways of hacking a solution anyway. In this article we will look at the simplest and probably least robust solution, changing the class of all TProgressBar instances at runtime.


Changing the class


On the face of it, you might think it is impossible to change the class of an existing object instance. The class of an instance is determined by two things; the class type T that the object reference was declared as at compile time and the class type D that was used to create the object instance and assign to the object reference. D must be assignment compatible to T and thus must be T or derived from D.

type
T = class
end;
D = class(T)
end;

procedure Foo;
var
Ref: T;
begin
Ref := D.Create;
end;

While you cannot change the declared type of the object reference, you can change the runtime type of an object instance. Why is this possible? Well, the runtime type of an object instance is stored in an implicit field of the instance memory - the first 4 bytes of an instance always contain the TClass reference (implemented as a pointer to the class' VMT) of the class that was used to create the object. This reserved field is initialized in the InitInstance class method defined in TObject.

class function TObject.InitInstance(Instance: Pointer): TObject;
begin
FillChar(Instance^, InstanceSize, 0);
PInteger(Instance)^ := Integer(Self);

This method performs other tasks as well (such as initializing all interface method table fields), but what interests us is that upon entry the Instance parameter points to a raw uninitialized block of memory (you can see that NewInstance calls GetMem and then InitInstance). This block is first cleared to all zeros by calling FillChar (this is what ensures that all object instance fields are 0, nil, false etc) and then overwrites the first 4 bytes with the TClass reference (which is available in the implicit Self parameter for non-static class methods).


Phew! Now that we know that the de-facto runtime class of an object instance is effectively stored as a field with a known offset (0), it is actually amazingly easy to change the class - we can just overwrite the TClass reference there with another TClass value. To ensure that things don't crash (the compiler makes assumptions about field offsets, virtual method indexes and so on based on the declared type of an object reference) we have to be careful to only overwrite the VMT slot with a class that derives from the current one. And since the object instance has already been allocated with a fixed size, we should not add any instance fields to the derived class.


Let's see how we can use this technique in the TProgressBar case. First lets declare and implement a TProgressBar descendant that fixes the Vista issue.

type
TProgressBarVistaFix = class(TProgressBar)
private
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd);
message WM_ERASEBKGND;
end;

procedure TProgressBarVistaFix.WMEraseBkgnd(var
Message: TWmEraseBkgnd);
begin
DefaultHandler(Message);
end;

It is basically identical to the interface-breaking, clean-solution code above - we only had to change the name of the class and declare that it inherits from TProgressBar. Then we have to write the code that takes an existing TProgressBar instance and changes its runtime class to TProgressBarVistaFix.

procedure PatchProgressBar(ProgressBar: TProgressBar);
type
PClass = ^TClass;
begin
if ProgressBar.ClassType = TProgressBar then
PClass(ProgressBar)^ := TProgressBarVistaFix;
end;

Note that it first checks to see that the actual class type is exactly TProgressBar and not some derived type - that would mess things up because TProgressBarVistaFix derives from TProgressBar.


One of the problems with this hack technique is that you need to explicitly patch each progress bar instance. You could do this in the FormCreate of all forms containing one or more progress bars, for instance. Or even in an button's OnClick event handler as I've done in a simple demo app.

procedure TForm5.PatchBtnClick(Sender: TObject);
begin
PatchProgressBar(ProgressBar2);
end;

If we were CodeGear (or if we feel adventurous) we could apply the patching to a single point in the implementation of the TProgressBar constructor.

constructor TProgressBar.Create(AOwner: TComponent);
begin
PatchProgressBar(Self);
// ...
end;

This hack can be useful when you need to quickly change the runtime class of an object on an ad-hoc manner. It has a number of disadvantages, however:



  • It requires patching each object instance explicitly
  • It changes the class of the instance. In our case ClassName will return 'TProgressBarVistaFix', for instance. One workaround for this would be to move the TProgressBarVistaFix class to a separate VistaFixes unit and name it TProgressBar. Still the RTTI and class reference will be different than the original class. 
  • It doesn't fix the problem for descendants of the patched class. In the TProgressBar case there are probably not so many of those around..?
  • To ensure that all object instances are patched, you need to have access to the source of and change the implementation of the original class' constructor.
  • The compiler creates a new VMT for the hack class, so the code size increases accordingly (yeah, who cares?, right).

The benefits of this hack is:



  • It is very simple in the sense that it does not require writing into any protected code pages (which would require the use of VirtualProtect or WriteProcessMemory).
  • It is easy to extend it to override any number of virtual, message and dynamic methods. It can also be used to promote properties to published so that RTTI is generated for them. Just update the hack class and rely on the compiler to build the proper VMT information for you.
  • It is flexible in that It is possible to patch only selected object instances instead of all instances of a class

Both this hack and the other variants that we will examine in upcoming blog posts have certain deficiencies that is hard to overcome due to the nature of the VMT and compiler. The hacks work well for leaf classes that do not have any descendants, but if you use them to override a virtual method in a non-leaf class (i.e. one that has descendants), and that virtual method is also overridden in one of the descendant classes, and the descendant method calls back to the inherited method, things will not work as if the method had been overridden in the clean way. Clear as mud? We'll revisit this topic later.


Conclusion


The hack presented here of changing the class of an object instance at runtime can be useful in a limited set of cases. Only use it for leaf classes and when replacing the class VMT with a new one doesn't introduce issues (such as ClassName changing). For most other cases, other hacking techniques would be better. We'll look at a couple of these in upcoming articles. Stay tuned! ;)


[Updated: reduced extensive "boldness" of the text ;)]

7 comments:

  1. Interesting stuff!

    One note. In "D must be assignment compatible to T and thus must be T or derived from D." you probably wanted to say "... must be T or derived from T."?

    ReplyDelete
  2. Very interesting (as usual!)

    It's a pity there's no way to 'insert' a class in a class hierarchy (so that all TProgressBar-derived classes at runtime end up deriving from TVistaProgressBar which genuinely derives from TProgressBar). I suppose this simply isn't possible?

    ReplyDelete
  3. Wow, that's one hack that would have solved quite a few problems I had a few weeks ago!

    It's a bit scary but goes to show how some people really know the inner workings of Delphi!

    Great post, thanks!

    ReplyDelete
  4. Great! But I have two questions:
    (1) You have written a patch and execute it in a constructor already. Why not modify the source code directly?
    (2) Fastcode project modifies source in another way ~> To replace the address of a procedure/function. What do you think about this technique?

    ReplyDelete
  5. Hi Stanley,

    > 1) Why not modify the source code directly?

    The exercise is to fix the issue *without* modifying the interface section - to stay binary compatible with already compiled .dcus and .bpls.

    Without that restriction it is way too easy and wouldn't warrant a hack blog post ;).

    > (2)replace the address of a procedure/function.

    As I said, stay tuned... ;)

    ReplyDelete
  6. Very good, really. It just helped me to get rid of a problem with TDBLookupComboBox, which doesn't have an "on change" event (but it got a protected procedure KeyValueChanged). So I simply made new class to inherit from TDBLookupComboBox, and added that event there, and then used your trick to make it work with "normal" TDBLookupComboBox. Works great in Delphi2009, thanks! :)

    ReplyDelete

Comments are moderated - spam and non-relevant links to will be deleted.