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.
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.
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.
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.