As far as I can tell, as of Delphi 2010 at least it is impossible to deploy a DBExpress application without also deploying at least one dbxdrivers.ini. Furthermore, deciding where this ini file has to go is a minefield, and the DBExpress architecture smells very badly in this area.
Sadly, this post won’t contain all the answers to the question it poses since I have not yet found those answers. The post is in part an effort to find someone with the answers – if they exist.
One project I am working on is being migrated from Delphi 2006 to Delphi 2010 (ultimately XE, hopefully, but 2010 is where I am at right now).
The application supports connection to a variety of databases, using the DBExpress framework. In Delphi 2006, connections were instantiated and configured dynamically.
Never mind whether having configuration parameters maintained in an external configuration file is A Good Thing™ or not – the fact is, this was not how the application worked, and it did work very successfully for some years.
Note also that the application is not an internal application but a commercial product, so the extent to which we can dictate the environment in which the application runs is necessarily limited.
Problems were immediately uncovered when we moved this code to Delphi 2010, and all revolve around DBXDRIVERS.INI file, or the lack there-of…
First, when setting a value for the TSQLConnection.DriverName property, if that value is anything other than an empty string, the property setter now insists on loading information from the ConnectionFactory for that driver.
In Delphi 2006 this only occurred at design-time, but it is now a behaviour also imposed at runtime.
There is only one ConnectionFactory, and that is the one driven by the INI file – DBXDRIVERS.INI
The result: If you have any code that sets DriverName to a non-empty string, you must now have a DBXDRIVERS.INI file, and the DriverName you set must now be a valid a driver name identified by that ini file.
NOT Setting DriverName
So perhaps we can avoid this problem by not setting DriverName, leaving this property empty and configuring the parameters that would be set from the ini file manually ourselves (as we always used to do).
Sadly, this doesn’t work either.
For whilst you can successfully leave DriverName unset and set all other relevant properties, things fall apart when you then try to then Connect the TSQLConnection.
At this point, DoConnect() will call CheckLoginParams() (unavoidable) and CheckLoginParams() contains this line of code:
if FDriverName = '' then DataBaseError(SMissingDriverName);
Somebody else suggested configuring the connection parameters in a BeforeConnect event, but this changes nothing about the flow of code that reaches this line of code.
Setting DriverName, even in BeforeConnect, will invoke DBXDRIVERS.INI validation of the specific value.
Not setting DriverName will always result in the TSQLConnection complaining that the property has not been set when it is eventually connected.
Which is right were we started.
Giving In To The Inevitable: Deploy and be Damned
So, it looks like we are forced to provide a copy of DBXDRIVERS.INI with our application.
That isn’t necessarily A Bad Thing™. Having the ability to add support for new drivers to our application could actually be an improvement, so rather than investigate how to avoid deploying DBXDRIVERS.INI, I instead set about looking into how to safely and reliably deploy it.
Oh dear, oh dear.
DBExpress starts to look very shonky when you prod and poke things in this area.
Critical to the whole thing is how DBExpress locates DBXDRIVERS.INI. This is critical because you have no way to explicitly direct DBExpress to use a given ini file or even look for DBXDRIVERS.INI in a specific location.
It follows it’s own rules:
1. Look in the application directory. That is, the directory containing your application EXE file.
In the days of UAC I am sure I don’t need to spell out why this is not only A Bad Idea™, but A Dumb Idea. A Very VERY Dumb Idea.
2. If no file found in the application directory, use the location stored in the registry key: HKEY_CURRENT_USER\CodeGear\BDS\7.0\DBExpress\DriverFilename
NOTE: You can change the value of this key, but you cannot change the name of the key.
3. If no registry key is found in HKEY_CURRENT_USER, or if the DBXDRIVERS.INI file is not found in the location identified by such a key, then look for the same key in HKEY_LOCAL_MACHINE.
NOTE: If you use either registry keys to store your DBXDRIVERS.INI file location, then you had better be prepared to share your INI file with every other DBExpress application on the machine. We would rather not have to do that.
But we don’t want to store DBXDRIVERS.INI in the application directory either.
Where Do We Go From Here
Ideally we would be able to continue configuring our TSQLConnections at runtime, dynamically. Just as we always used to. We can extend our own application very easily to support additional drivers not known at compile time – and since we will need to store those configuration details in a database, this would actually be preferable to maintaining an INI file separate/in addition to that database.
Failing that, we need to be able to direct DBExpress to use an INI file of our choosing, or at the very least direct DBExpress to use a DBXDRIVERS.INI file in a location of our choice.
These seem like pretty basic requirements to me.
One of which used to be possible, the other which should have been made possible once the alternative was taken away, and ideally in addition to the alternative in the first place.
But, I also have to say that in exploring the code in SqlExpr, I am not impressed by what I see.
Not one little bit.
It is worrying that these simple, and to me: obvious, design flaws are present in the framework in the first place.
It is downright scary to see the mess that the code is in that implements the design choices that were – for whatever reason – made.
Just one example that immediately springs to mind, and doesn’t need a lot of explanation:
TSQLConnectionFactory is an abstract base class for all connection factory implementations. There is only one such implementation – the one that sits atop the INI file(s).
So it is not obvious to me why the abstract base class contains code to locate and identify INI files, which are supposedly part of the implementation of the specific INI file based derived class.
Other examples include very suspect indentation that smells very badly of incorrect conditional flow control, but it is difficult to say for sure that this is indeed what that indentation indicates, obviously.
Similarly the amount of commented out code (commented out without explanation) is worrying – such artefacts smell of experimental/incomplete code in my experience.
But I digress.
The point of this post was specifically around the deployment issues relating to DBXDRIVERS.INI and how to avoid those issues or make the best of a bad job.