Francois Piette recently posted a solution to obtaining the screen position of a menu item involving using a “hacker” class. There is however a safer, more direct mechanism which I hope Francois won’t mind me sharing and a far less safe related hacking technique that his post brought to mind.
A Puzzle Wrapped in an Enigma
Francois’ original puzzle was an interesting one to solve: how to determine the position on screen of a given menu item. I have to say first initial instinct was to think that the Windows API must provide access to this information more directly.
And it does. 🙂
To obtain the screen co-ordinates of a menu item you can simply call the GetMenuItemRect() API in Windows. All you need is the HMENU of the menu and the index of the item of interest.
For reliable position information the menu item should be visible, which is of course going to be the case if you call this API in response to an OnClick of the menu item in question.
Here’s a revised version of Francois example with a simple form which presents a modal version of itself at the position of either of two menu items on the File1 menu (FileItem1 and FileItem2 both share the FileItemClick event):
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Menus; type TForm1 = class(TForm) MainMenu1: TMainMenu; File1: TMenuItem; FileItem1: TMenuItem; FileItem2: TMenuItem; Close1: TMenuItem; procedure FileItemClick(Sender: TObject); procedure Close1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Close1Click(Sender: TObject); begin ModalResult := mrOk; end; procedure TForm1.FileItemClick(Sender: TObject); var rc: TRect; form: TForm1; begin GetMenuItemRect(Handle, File1.Handle, File1.IndexOf(TMenuItem(Sender)), rc); form := TForm1.Create(self); form.Left := rc.Left; form.Top := rc.Top; form.ShowModal; end;
There’s no attempt to code this correctly – freeing the modal forms properly etc. The point is the menu item rectangle determination.
Which isn’t to say that Francois’ hacker class technique doesn’t have it’s uses. It absolutely does, in Delphi.
Apart from anything else, it is an alternative to “class helpers” that is not vulnerable to the “Highlander” constraint constraint those suffer from (“There can be only one [in scope]”).
But Francois’ post also stirred a distant memory of a related technique.
Hackers vs Crackers
Francois calls his sub-class a “hacker”. I prefer to call classes that exploit this particular “protected extension” behaviour “crackers”, since they “crack open” a class for access to protected members but are otherwise entirely safe.
After all, they are more or less formally supported – though less well imho – by “class helpers”.
I do have another type of class which I term a “hacker”, which is far, far less safe (diabolically unsafe would be nearer the mark) since they provide access to the private members of some other class. Furthermore, classes exploiting this technique are vulnerable to changes in the “target” class in different Delphi versions.
You start by declaring a class but in this case instead of the target class you derive from the target class’ own ancestor.
So, to hack a TMenuItem you would extend TComponent since this is the class that TMenuItem extends:
TMenuItemHack = class(TComponent)
You then reproduce, verbatim, the member declarations of the target class, down to and including any private member you wish to gain access to. For the sake of argument let’s assume we wanted to directly access the FRadioItem private member of a TMenuItem (this is a purely hypothetical example):
TMenuItemHack = class(TComponent) private FCaption: string; FChecked: Boolean; FEnabled: Boolean; FDefault: Boolean; FAutoHotkeys: TMenuItemAutoFlag; FAutoLineReduction: TMenuItemAutoFlag; FRadioItem: Boolean; end;
Assuming you have a correctly declared hack class, you can now use this class to type-cast a reference to an instance of the target class and thus directly access the private members up to and including the last one declared.
isDefault := TMenuItemHack(SomeMenuItem).fDefault; TMenuItemHack(SomeMenuItem).fDefault := FALSE;
If you place the hack class in a unit other than the unit where it is used, you will need to change private or protected visibility specifiers to public for the member(s) of interest.
As I say, this is highly sensitive to Delphi versions. This is the declaration from Delphi 2010, which may be the same in a number of other Delphi versions, possibly all. But if the member declarations of your target class vary in order or content in some other version then you must reflect those variations in the declaration when compiling using those other versions.
Based on experience of this technique in the past*, you can omit any function or procedure declarations even if they occur between member variables. All that seems to matter is that you have all the member variables themselves.
(* – don’t ask. It was on a project a joined and, to be fair, as unsavoury as it is it was necessary in that case with that particular version of Delphi to enable a particular behaviour in the application)
It is also potentially vulnerable to variations in compiler behavior that may impact on the member layout of class member variables. This is perhaps unlikely, though it is possible (though again, not something I have encountered) that the declarations in the VCL source do not correspond to the actual declarations in the pre-compiled VCL unit that the compiler will use (unless you have contrived to redirect the compiler to your own copy of that unit, which is a whole other minefield!).
As I say, the technique is utterly unsafe and relies on the memory layout of two classes with the same ancestor and the same member variable declarations being identical. The “hack” class is – in effect – overlayed on top of the memory of an instance of the target class.
You really are digging around in the guts – the physical memory – of the target instance, and if you get it even slightly wrong there is no telling what the impact might be.
But it can be a life saver if you absolutely must gain access to some private member that is not exposed in a way that you need.
Your hacker class is something I’d NEVER try. There’s always always another way even if it means saying “that can’t be done”.
Well, it’s not my class as such, just something I have had to work with in the past. I’m 100% with you that it is not something to be recommended and absolutely should be avoided if at all possible. But the notion that there is “always another way” or that if there isn’t that “it can’t be done” is an acceptable answer is simply naive.
You can’t always stick your fingers in your ears, cover your eyes (a neat trick if you can do both at the same time :)) and pretend that the nightmare isn’t happening and will go away. 🙂
The problem on that occasion was a bug in the way that forms were initialised when visual inheritance was involved. The solution was to “clobber” certain properties during initialisation and then restore them to their intended values at appropriate points. The methods needed to override to achieve this were protected, so no problem there, but the only access to the properties that had to be changed was via the protected/public/published mutator methods which not only changed the properties but also triggered behaviours intended to be associated with such changes, and it was precisely those behaviours that needed to be avoided.
There were two choices at that point: Tell the customers “Tough, we can’t fix it” or, well, get on and fix it. 🙂
The third option of telling the customer that the problem had been identified but that fixing it was going to be offensive to the developer’s fragile sensibilities I don’t think was ever really on the table. 😉
I should again make it clear that this pre-dated my time on the project. By the time I arrived it was just something that had to be understood and lived with. There really wasn’t an alternative solution at that time (believe me I looked for one, when I found this in the code!).
The problem in the visual form inheritance implementation may well have been solved in the meantime in some later version of Delphi, rendering the technique redundant. All I know is that it was introduced to address a problem that was present at least in Delphi 5 and that problem remained at least as of Delphi 7, which is where things were when I left.
This reminds me of even deeper hacking I did back in 2007, nicely documented by Hallvard Vassbotn : http://hallvards.blogspot.nl/2007/05/hack17-virtual-class-variables-part-ii.html