Toybox
by Peter Wone - Independent Developer
Well, I did the deed. Chucked in the towel with my day job and started a business. Not in that order, of course. The science of applied cowardice dictated that things should be thoroughly organised in advance and all bets hedged.
But the other half just bought me a copy of The Dilbert Future, and one of Scott Adams’ whimsical predictions is this:
In the future, skilled professionals will flee their corporate jobs and become their own bosses in ever-increasing numbers. They’ll become entrepreneurs, consultants, contractors, prostitutes and cartoonists.
Not only that, but part of the reason I left was a pointy-haired boss!
I seem to be living in a Dilbert cartoon. Scary.
It had to happen. My most interesting recent doings happened in my spare time, of which there has been precious little, although of course this has been due in part to the demands of setting up Wombat & Me.
I am constantly amazed how many opportunities there are, if you have a few skills and can think for yourself. If I didn’t remember first-hand from the time between uni and my first IT job, I’d wonder how anyone could be unemployed.
You may recall that last issue I wondered about using the Midas support in Delphi3 Client/Server to make a server out of the Jet engine. Now I’m beginning to have second thoughts, although I’m not yet ready to discard the basic idea. The problem is that the Jet engine, while logically re-entrant, doesn’t handle multiple concurrent requests all that well. They execute correctly, but they execute serially.
The version used by Exchange is more different than I realised. It’s sufficiently different that the BDE (Borland Database Engine) wouldn’t talk to it, and I haven’t been able to get hold of documentation for it – "That version of the Jet engine isn’t supported. It was never intended for release to third party developers." Uncle Bill isn’t silly, it seems. I should have known.
Never mind. The BDE itself is multi-threaded, with support for custom datasets, and there are various redistributable ISAMs out there. That’s more work than I anticipated, but hey, welcome to IT. It’s always more work than we expected.
Another quick plug for Delphi – events, events, events. Lately I’ve been writing a lot of custom controls. I remember all the grief it was in VB to handle an event for which there wasn’t an event (if you know what I mean). Good ol’ Dan Appleman gave us SpyWorks, without which we would have been totally up the creek, but what a mess! And no true inheritance, so everything a one off. Problems never stayed fixed. What code re-usability? We tried. Without inheritance, it’s a con.
In Delphi it’s as simple as defining a message and binding it to a windows message. This is too easy –
procedure ANewEvent(Msg:TMessage); message WM_USER_MYNEWEVENT;
For descendant classes it’s even easier. You do nothing, just inherit. Or you can subclass it if you want to. Try doing that with a cheaper foil. My toughest obstacle these days is not getting carried away. Why are you still using VB?
And now for something completely different – for a change I have something nice to say about VB. About ASP, actually. Using client-side VBScript is not a great idea; it limits your audience to Internet Explorer users. But ASP (server-side VBScript) is a great idea. As a macro language, it’s impressive. And its inherent limitations, such a dreadful liability in a real programming language, are an asset in an environment subject to attack.
But besides embedding interpreted code in web pages, there’s not much you can do with VB that you can’t do with Delphi in much the same way with much the same effort, or (frequently) somewhat different with much less effort.
More to the point, Delphi is inherently extensible, whereas VB is not. All right, it is, but to extend VB you have to switch programming languages. To extend Delphi, you use … Delphi. Admittedly you do change the way you go about things, but for those of us who habitually write custom controls, it’s no sweat, hardly even a mental change-gears.
Wheels within wheels
Here’s a bit of a how-to for constructing controls out of other controls.
Quite often I want to use graphical controls (as opposed to windowed controls) because they put much less strain on the resource pool. But to implement the sexy Office97 toolbar effect of controls showing 3D borders only while the mouse is over them, you need MouseEnter and MouseExit events.
MouseEnter isn’t too hard. You keep a FMyCtrlMouseOver flag. When the flag is false and you get a MouseMove event, the control has been entered so you call the MouseEnter method of your control, which sets FMyCtrlMouseOver true and calls FonMouseEnter if a user handler has been assigned.
But how do you detect the mouse leaving the control?
Mirror, mirror on the form, who’s even lighter weight than norm?
If it’s a windowed control you can capture the mouse. When a MouseMove event occurs outside the BoundsRect of your control, you fire a MouseExit event and release the mouse.
Unsurprisingly, non-windowed controls don’t have window handles. Out of this comes a problem with detecting the mouse. You do get MouseMove events (How? The answer to this question is the key to solving this problem!) but it’s a bit tough to tell when the mouse leaves the control’s airspace because you can’t capture the mouse. SetCapture takes a window handle as a parameter.
There’s not much you can do about this, because what SetCapture actually does is make Windows address all mouse messages to the specified window handle. The MouseMove events you get with Delphi graphical controls are handled by proxy. The container gets the event, and its message pump checks whether the event took place within the bounds of a graphical child. If so, it invokes the MouseMove method of the graphical child.
The trick is that once you understand this, there’s nothing stopping you from doing it yourself. You capture the mouse to your container’s handle, and then you work out which child should get an event. Making four comparisons for each graphical child makes for a lot of messy conditionals, so wrap it up like this:
function InCtrlRect(Ctrl:TControl; X,Y:integer):boolean; begin with Ctrl.BoundsRect do Result := (X>=Left) and (X<=Right) and (Y>=Top) and (Y<=Bottom); end;
Before you point out that I could have used Parent.ControlAtPos(Point(X,Y)) let me remind you that you can’t do this if you plan to make an ActiveX control. If we were designing exclusively for Delphi, we could simply have bound handlers to CM_MouseEnter and CM_MouseLeave, which are thoughtfully furnished by Delphi control containers.
When your container gets a MouseMove event, you walk its collection of children checking whether the event belongs to each child. If you have a small and fixed group of children, as is normally the case when constructing custom controls, you can check each explicitly.
Here’s an example. Note that TCombo97 must be both Owner and Parent of its children. If you specify AOwner instead of Self when creating the child controls, they can be selected in the design environment, a highly undesirable situation. Also, the capture will apply to the form rather than to the container, so things won’t work quite as anticipated.
procedure TCombo97.BtnImageMouseEnter(Sender:TObject); begin FBtnImageMouseOver := true; SetCapture(Handle); DrawButton(FGlyphDrop,bvRaised); DrawContainerBorder(bvLowered); end; procedure TCombo97.BtnImageMouseExit(Sender:TObject); begin FBtnImageMouseOver := false; ReleaseCapture; DrawButton(FGlyphDrop,bvNone); DrawContainerBorder(bvNone); end; procedure TCombo97.BtnImageMouseMove(Sender:TObject; Shift: TShiftState; X, Y: Integer); begin if not FBtnImageMouseOver then BtnImageMouseEnter(Sender); end; procedure TCombo97.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited; if FBtnImageMouseOver and not InCtrlRect(FBtnImage,X,Y) then BtnImageMouseExit(FBtnImage); end;
This issue’s challenge: do the same thing in VB. Believe it or not, you can do this in VB – in roughly the same amount of code, and in much the same way. This is essentially an exercise in multiple inheritance using aggregation. But having built this object, you can’t inherit from it. Unless you use Delphi.
As a matter of interest, this business of using a windowed container as a proxy for message handling is how non-windowed ActiveX controls work. All ActiveX controls are hosted in OLE containers, which (to the best of my knowledge) invariably have window handles. Therefore an ActiveX control need not have its own window handle to respond to messages. In the example I have given, the container has knowledge of its children and their methods, however this is not absolutely necessary. I just did it because it simplified the example code. And because I’m lazy.
Caveat emptor
I hereby invoke the second law of programming - everything is more complicated than you thought. (A slightly sanitised version of the first law is "If it works, don’t mess with it.")
The event capturing instigated by SetCapture doesn’t only apply to the mouse. You have diverted all Windows messages to the handle you specified. You must process everything. Alt-F4, for example, won’t work. We can’t send messages – they get sent straight back to us – and we can’t call parent methods – ActiveX, remember? This leaves us with a bit of a problem.
KISS
After I wrote all the foregoing, it dawned on me that if I created the button as a separate control and used it on a TCustomPanel (or other Delphi container control) then I would receive CM_MouseEnter and CM_MouseLeave. Then it all got simpler.
The lesson in this is that if you want things to be simple you must make them simple. Solve one problem at a time. Often it’s tough to identify the separate problems, which is what wasted so much time for me. I kept thinking "it’s not really a button" and trying to handle it as an illusion created by the painting of the control on which it appears. That got all the issues of being a button tangled with the issues of being a panel. Bad karma. A learning experience, and embarrassing, since I’m already supposed to know that.
It’s all part of the uneasy truce between science and technology. Science is the evolution of ideas, in which non-viable variants are weeded by harsh reality, selecting in favour of ever more effective generalisations. Technology is the application of what we know. Technology doesn’t involve new ideas. It involves new uses of existing ideas.
What I was trying to do was already supported by Borland’s technology. (Pity it isn’t supported by Microsoft’s technology.)
And now for something completely different
Back to my pet database project. As many of you may recall, I’m not really happy with existing database server technology. It’s overpriced, performance under load isn’t great, most of it has locking problems, it’s fragile, it’s typically horrible to configure and worse to use.
Performance with big datasets
It comes down to working set size management. This is a kind of cache management.
Once (several times, unfortunately) I had occasion to run update queries on about four million rows of about 1024 bytes each. Four gigabytes of data was read and written back to disk. We had half a gig of RAM on the server. Some of you may have more but I bet you don’t have four gig. Now, this was using Microsoft SQL Server. I got an implicit transaction whether I like it or not. SQL Server is not a versioned database, so the modified records could not be written back to disk until the transaction completed.
They could be written to the transaction log, but there were group totals involved, so I had a 4G working set. Thrash city. See you next week. I had to expand the log and make sure it was check-pointed and dumped, or the server would crash. And it was just as likely to run out of locks and abort the transaction.
This was not an hypothetical situation. And there are bigger datasets around. That was data from just one month. Year to date involved anything up to 48G of data. To handle it required a complex work-around with intermediate result tables. Barf.
This would not have happened to a versioning database engine – for example, Borland Interbase. In a versioned database the modified rows are written to disk immediately. You can’t run out of locks because you don’t have to use them. You can’t overflow the log because there isn’t one – it’s not even useful unless you’re replicating with a change log, and there are other ways to skin that particular cat. The irony is that the company facing these problems had just replaced Interbase with SQL Server "for performance reasons."
I tried to persuade them of the error of their ways, but it was not to be. The company that had previously handled their billing system had used (abused?) Interbase, and had built UI software that (they told me) left much to be desired. They said that they had tried Interbase and found it wanting. At the time, being new to their system, I reserved judgement on the basis that they habitually worked with enormous datasets and probably knew what they were talking about.
Experience has shown me otherwise. In the ghastly clarity of hindsight, I think their rejection of Interbase was emotional rather than reasoned, and their refusal to consider any other possibility merely a standard human response to being caught out making an expensive mistake. Either way, the political decision to use SQL Server stuck. At least it’s no longer my problem. Eventually they will either learn or die.
Two arguments posed to me against Interbase were a dearth of admin tools, and Borland’s habit of altering the metadata schema in ways affecting compatibility between versions. Of course, Microsoft would never do that to us. Much. And the SQL Explorer and Database Desktop tools shipped with Delphi Client-server edition are so similar to the Microsoft SQL Server admin tools that it’s not funny.
So what’s wrong with Interbase? Mostly the Borland stealth marketing plan, idiotically high prices, counterproductive licensing schemes and a lack of tools if you don’t happen to have Delphi Client-server.
Borland is very good at architecture. Microsoft is very good at sexy interfaces. What’s more important? Architecture, of course. What do people see? Interfaces. What do they remember? What they’ve seen. What do they buy? What they remember. (Or worse, the product from the company that gave your boss a polo shirt and a nice mug at the last trade show.) All of which is why Microsoft succeeds in pushing junk on us. You don’t think they’re pushing junk on us?
In my considered opinion, Microsoft Exchange (version five, which hasn’t been released yet) is nearly as good as the SMTP post-offices that Unix has had for, um, must be nearly twenty years now.
For several thousand dollars you can have a post-office which is compatible with itself and SMTP post-offices – but not with UUCP post-offices, of which there are a surprising number on the net. Or for forty dollars you can have an SMTP post-office which is compatible with the entire net (except for Exchange servers earlier than version five) and which is more robust and delivers substantially better performance on any given hardware, particularly under load. I’m talking about the Red Hat Linux post-office.
A quick plug for Linux – which is basically Unix. The entire operating system is freeware. The forty dollars covers the six CDs it occupies. Deploy it as many times as you like. You get source code for everything. You can remote administer every aspect of the operating system from anywhere on the net. Properly configured Linux systems measure up-time in years.
The down-side is that development tools for Unix are not what they could be.
Make no mistake; Unix is a server operating system. Personally I still run Win95 as a client operating system.
Delphi for Unix?
And another change of subject – Delphi is the best all-round development environment for the Windows platform. You can argue that, but you’ll be wrong.
At its core is a fast, stable Object Pascal compiler in a nice mature version 10. That’s not enough on its own, though. What makes it into a tool of choice is an awesome object library designed for extension by the people who use it, which is in its fourth major revision, and a pretty good developer workbench (IDE). Let’s be fair here: the IDE is a rip-off of the one in Visual Basic. Microsoft is very good at interfaces, and an IDE is nearly all interface. Credit where credit’s due.
But Delphi has all the pieces, and that’s what makes it the best.
My number one complaint about Delphi is that it’s tied to the Wintel platform. What I would dearly love is Delphi for Unix. Joy of joys, Borland seems to be about to deliver essentially that!
Version ten of Object Pascal introduced COM-compliant interfaces. If that doesn’t mean anything to you, don’t worry about it. The important point is that interfaces were the main structural difference between Borland Object Pascal and Java, and now that difference is gone. I think that Borland is tooling up to ensure a high degree of functional portability between their Delphi object library and the one they’re preparing for Java.
As long as you don’t get sucked into dependence on Wintel specific technologies like ActiveX, anything you develop using JBuilder should run unmodified under all flavours of Unix, all flavours of Windows and on Macintoshes.
I also believe that Microsoft is busily weaving ActiveX through their Java libraries expressly to prevent developers from escaping. Conspiracy theory? Well, yes, but only because that’s what I’d do if I wore their shoes. And they are doing it, and that is the net effect (sic). So the only thing I could be wrong about is the motive.
Sun is suing Microsoft. Mark and I discussed this one evening. Mark thinks this is a case of the kettle calling the pot black because Sun is trying to dictate the environment – the software environment shall be thus, no more and no less – but I think he’s wrong.
First, I don’t think control is their primary agenda. Suing Microsoft gets them two things.
1.David and Goliath. Sun gets to be David.
2.The front page. Sun gets to be David, on the front page.
There is also the possibility that Sun might win.
It’s not a question of good or bad, right or wrong. It’s a question of whether Microsoft is in breach of contract, which only the courts can determine. Besides, after reflecting on Mark’s comments for a while I have to say that if it came to a choice between Sun and Microsoft dictating the environment in which we all work, I’d have to pick Sun every time.
The Java AWT gives us the same thing as the Windows API, but it’s a whole lot smaller. It’s better organised, better documented and better controlled. The documentation is much smaller than the WinAPI documentation because the AWT is simpler.
Message in a bottle…
The message queue in Windows is a wonderful thing. It provides for asynchronous notification and fail-safe invocation of methods – these can even be broadcast to whole tribes of objects.
But only for window objects. Windows messages are addressed to window handles. Why? What gave Microsoft the idea that windows were the only objects in need of asynchronous notification?
Messaging is basic to the object-oriented model. So why doesn’t anyone implement it at the very root of the object hierarchy? I’m serious. I think that messaging capabilities should be implemented in TObject (or whatever it’s called in your favourite language).
I was in the process of designing a messaging object so I could retrofit messaging by containment, when the mess I created started to look familiar. I’ve seen this particular mess all through the Microsoft Frustration Classes. What you end up doing is using a windowed control as a messaging proxy.
We could improve this situation by using a single invisible windowed control as a shared messaging proxy for the entire application. We could include in each message information identifying the control for which the message was really intended.
But in the case of genuine window messages meant for windowless display controls like labels, we can’t really do this unless we want to pack and unpack them, or supply a method address so that we can use callback. Only this isn’t going to work for broadcast messages…
What I’d trying to show is that we need to be able to send messages to any type of object. Moreover, it may not be obvious when an object is designed, that something external might need to invoke its methods asynchronously or as part of a broadcast.
I’d call upon the powers of darkness to fix this sorry state of affairs, but I can’t even get Microsoft to embrace inheritance, never mind common sense, so I’ll call upon Borland and Sun to ensure that their base classes support asynchronous messaging.
Please, O Borland and Sun, do this for your own programmers, if not for me.
Back to the future
The world has already invented GUIDs, so why not use them? Admittedly they’re intended to identify classes rather than instances, but that doesn’t mean we couldn’t plaster another 32 bits on the end to identify instances. (That would give us 160 bit identifiers – big enough for ya? J )
The reason I choose DCOM GUIDs is that there is no particular reason for messaging to be confined to the local PC. They identify a class in space and time – the first part of the GUID is a machine identifier, intended to be globally unique. The remainder is a sort of timestamp hash from the local clock when the class was registered.
The messaging subsystem could routinely compare each message’s GUID with the equivalent of a subnet mask, and when necessary dispatch it across the network without any special treatment by the programmer.
Surely this fits neatly with Sun’s vision of network computing.
I leave you with it.
JBuilder
Since I first drafted this issue's column, Borland has released JBuilder.
I have been promised a copy of JBuilder Client/Server (goody goody!) and I've already had a cursory look at JBuilder Professional. So far so good. At first glance the class libraries don't seem as complete as the Delphi class libraries, but this is early days, and not everything can be ported away from the platform.
But the guys at Borland are accomplished masters of class library definition, and the language isn't structurally wanting, or alien to them. Soon I'll either be a very disappointed lad, or living in one of my favourite fantasies.
Stay tuned…