A site devoted to discussing techniques that promote quality and ethical practices in software development.

Wednesday, November 15, 2006

Fixing up Borland Delphi's COM Server Registration Problem

The story where Borland does not even understand their requirement for COM Server registration and then translating that to code has been described in the most kiddish format in my other blog message.

I doubt Borland will have the courage to admit mistake and fix their problem as this problem exists in their product unchanged since Delphi 3 to the latest, including those not yet released product. Hence it is a dead loss trying to get Borland to do something. I am going to show you how to fix this problem. It is as easy as learning "Mary has a little lamb" rhyme.

Before I'll show you the fix, let me reproduce their documentation partially here:
Start mode Switch Meaning
smAutomation embedding The application was started by Windows in response to a request from an automation controller.
smRegServer regserver The application was started only to add the server to the system registry.
smStandalone
The user started the application as a stand-alone, interactive application.
smUnregServer unregserver The application was started only to remove the server from the system registry.
Table 1 - Permissible COM Server switches

For running the automation server as a stand alone application, you do not include any switch.

From the above requirement, it seems pretty obvious that you only have to run through the COM registration manipulation code when you encounter the switches /regserver or /unregserver. Incidentally this is the standard stuff if you build the COM local server using VB6, MFC and ATL.

Now it is time to reveal how Borland handles this situation badly. To see this you can locate this code fragment in ComServ.pas at around line 373, if you have access to Borland's Delphi tool or simply search for TComServer.Initialize in Unit ComServ. It is reproduced here in Listing 1,
// Listing 1
procedure TComServer.Initialize;
begin
try
UpdateRegistry(FStartMode <> smUnregServer);
except
on E: EOleRegistrationError do
// User may not have write access to the registry.
// Squelch the exception unless we were explicitly told to register.
if FStartMode = smRegServer then raise;
end;
if FStartMode in [smRegServer, smUnregServer] then Halt;

ComClassManager.ForEachFactory(Self, FactoryRegisterClassObject);
end;
In merely 9 lines (ignoring comments), the two bold red lines are erroneous and I am going to show you why.

Let's take the bug number 1 and that is the COM Server registration code (the first line in red).

If you assign the values listed in column 1 in table 1 to the variable FStartMode, one value at a time, and then ask yourself under what value does the function UpdateRegistry() is not called.

If your answer is that UpdateRegistry() is called regardless of the value of FStartMode, then you are correct and you begin to see how silly that piece of code is.

UpdateRegistry() is a procedure that executes COM registration steps when the parameter is true. Otherwise it performs COM unregistration steps.

If you have a basic understanding of COM, you will realise that by the time COM successfully launches your local server via a client's call to COM method like CoCreateInstance(), your server must have been registered properly. So in that situation a Delphi automation server will have a FStartMode = smAutomation.
If you have followed me so far, UpdateRegistry() is called regardless of the switch and this also include the situation when you, the COM server, are launched by COM. So why do you then have to update the registry again? Doing so is like you have just flew into an airport and you still insist on buying an inbound ticket. It is silly.

To fix that all you have to do is to check if the FStartMode is smRegServer or smUnregServer. If so then called UpdateRegistry().

Now the second bug requires one to perform a function calls tracing code review. But in short, no one in the possible call chain throws EOleRegistrationError the one that Borland has programmed to catch (see the second line in red in Listing 1). Instead the code throws EOleSysError when there is COM registration error.
If you look up Delphi documentation you can see that these are two distinct exceptions both derived from EOleError and that they are siblings to EOleError.
So the corrected code is as follows, Listing 2:
// Listing 2
procedure TComServer.Initialize;
begin
try
if FStartMode in [smRegServer, smUnregServer] then
UpdateRegistry(FStartMode <> smUnregServer);
except
// on E: EOleRegistrationError do
on E: EOleError do
// User may not have write access to the registry.
// Squelch the exception unless we were explicitly told to register.
if FStartMode = smRegServer then raise;
end;
if FStartMode in [smRegServer, smUnregServer] then Halt;

ComClassManager.ForEachFactory(Self, FactoryRegisterClassObject);
end;
I have left Borland's incorrect catch statement there for comparison. Comparing this to that shown in Listing 1, you can see that it is pretty obvious that someone must have accidentally deleted the test for /Regserver or /Unregserver (the line in greed) or someone must be high on illegal substance and thinking what is left in Borland's code is a pretty cool optimisation.

As mentioned, many people have reported this bug before and Borland showed a complete lack of consumer concern to fix this problem. Incidentally, once you have registered, the subsequent calls to UpdateRegistry( true ) does not cause problem in LUA because COM's RegisterTypeLib() performs a read check to see if the COM information is the same. If so it does not update it. Otherwise it does. When that happen, you need power users or higher privilege.

If you want to replace Borland's buggy code, all you have to do is:
  • Take a copy of ComServ.pas to somewhere safe.
  • Edit the TComServer.Initialize to that shown in Listing 2.
  • In the Delphi's COM Server project, include a copy of this file.
  • Delete all the dcu file and close the entire project/group. Delphi IDE has this crazy frequently happening moment of memory lapse that it fails to pick up that your project now has a new file. Pretty dumb stuff.
  • Reopen this and rebuild it.

This automation server should now work properly. The best way to test this is to unregister the COM Local server (just the executable) in an Admin account. Then start the automation server as a stand alone in LUA. In this situation, the program should start up properly. If this is built using Borland's buggy code, it will throw an exception and that is not the one Borland programmed to catch.

It is this simple to fix Borland's bug!

4 comments:

Anonymous said...

Keep in mind a software company, in particular, is really its employees thus repeatedly insulting the entity is unlikely to be the most effective way to instigate change.

Dinther said...

on you mate. I had to deal with the exact problem only a few months ago. Frustrating that a Professional software development tool that I invested so many years experience in get's treated this way. Borland treated Delphi badly and over the years it has slipped from a superior commercial development platform to a hobbist tool because realistically you can't trust commercial development to a tool that is maintained so poorly. So good on you kick 'em in the goolies. Borland deserves it after ignoring such a blatant stupid error for so long.

EMauro said...

I also had the same problem.

That only show the completely disregard of Borland to their users. Instead of providing a stable and robust tool, fixing bugs and improving the code, they keep adding new useless features just in order to sell upgrades.

L. Mar said...

I would like to add some additional comments:
1) Some may read my blog message as an attack on the Borland's staff. There's never any truth. Any comment is directed squarely at Borland/CodeGear.

2) I have exhausted all revenues available to me to get Borland to recognise their mistake (no one writes perfect code) before I blog my experience, including putting up with some very arrogant, indifferent and ignorant support feedbacks.

There isn't too many situations in my software design activities that involve using partly with Delphi .Net that my .Net design come together without encountering Borland's own special treatment.

I am just following ECMA standard and yet there are so many cases of violation all in the guise of providing Borland Advantage.

This COM issue is just one of many. The others are documented in my other stale Blog.

My advice is to be on the lookout and know the technology you are using so that you'll not get push around.

Blog Archive