Using Paradox Tables on CD-ROMS and Other Read-Only Media
Sample Code is available for this article

You'll notice that Delphi database development leans heavily towards desktop databases; especially towards Paradox databases. Why? Most folks just don't have the need to access server-based databases. Also, Paradox, having been a former Borland product, was and has been the de facto desktop database format for Delphi since Delphi's introduction to the market. I've worked with Paradox since it first came out, and that's over 12 years, and I still prefer it over the likes of Access and dBase and dBase clones. Why? Because there are just too many features in Paradox that I'd be stupid not to use - things that don't exist in the other databases (I won't go into a feature comparison here, because that's beyond the scope of this discussion).

In any case, even though I believe Paradox is by far the best desktop database format around, it isn't without its shortcomings. One of them in particular is the inability to easily use Paradox tables on read-only media such as a CD-ROM or network directory that's set to READONLY. The reason for this is the way Paradox was built to address multi-user database applications. Whenever, you open up a Paradox table, two files are created in the directory where the table resides. These files are called Paradox.LCK and PdoxUsrs.LCK. The first file is the table/record lock file which keeps track of the users accessing the table, and the second file is the directory lock file. On read-only media, these files can't be created - that poses a problem, but not so bad that we can't work around it.

The workaround is this: All the BDE needs to access a Paradox table on read-only media is the PdoxUsrs.LCK file. Why? When it sees this file, it assumes it's already been written, and won't try to write it or the Paradox.LCK file again. It also assumes that the table is read-only, and so it won't bother. So all you have to do is create a PdoxUsrs.LCK file and you're all set. So how do you do that? There are two ways: One easy, one kind of easy. I'll let you decide...

Creating a PDOXUSRS.LCK File

The Easy Way

The easiest way to create a PDOXUSRS.LCK file is to simply open up a table. As soon as you do that, both the Paradox.LCK and PdoxUsrs.LCK files get created in the directory where the table resides. Then, what you want to do is open up either Explorer or File Mangler, and copy the PdoxUsrs.LCK file to another directory. Don't try to move it - you'll get sharing violations. That's it. You now have a dummy PdoxUsrs.LCK file.

The Sort of Easy Way

The other way to create a PDOXUSRS.LCK file involves a bit of BDE coding. Specifically, a call to dbiAcqPersistTableLock.This function takes two arguments: A Database handle, a Table Name (PChar), and the driver type (PChar). What it does is create both the Paradox.LCK file and the PdoxUsrs.LCK file. Pretty slick, huh? I suggest that you download the demo program now so you can follow what's going on in the code below:

procedure TForm1.Button2Click(Sender: TObject);
  DBs : TDatabase;
  if (Edit1.Text <> '') then begin
    DBs := TDatabase.Create(nil);
    with DBs do begin
      Params.Add('path=' + Edit1.Text);
      DatabaseName := 'MyLockDB';
      DriverName := 'STANDARD';
      Connected := True;

    Check(DbiAcqPersistTableLock(Dbs.Handle, 'MyLockTable.DB', 'PARADOX'));
    if FileExists(Edit1.Text + '\PDOXUSRS.LCK') then
        ShowMessage('Lock File was successfully created in ' + #13 + Edit1.Text);
        MessageDlg('Lock file was not created! Something went wrong!', mtError, [mbCancel], 0);

So what did I do? Well, the first thing was that I created a TDatabase. You need that to get a connection into the BDE. Note that I supply a "Path" parameter to the Params property. This will define where I want to place the lock files. And as with most data access components I use, I prefer to create and destroy them on the fly instead of embedding them on a form. There are a couple of reasons for this. First of all, it's good resource management. By creating the object, using it, then immediately destroying it, I make better use available resources than if I loaded everything in memory at once and kept it there until the program ended. Keep that in mind when you're building applications. Now onward!

Once I make the connection to the database, I make the call to DbiAcqPersistTableLock. Notice that I enclose the function within the Check function. Check solves most of the problems of trapping BDE error messages. In the old days before Check, you had to trap error messages yourself which, in many cases, took up several lines of code. In fact, many of my programs written in Delphi 1.0 that used functions that employed BDE calls were mostly error message trapping! Yikes. So a rule of thumb is to enclose all your BDE calls within  Check. It'll trap the error messages and pop up an error dialog. Why don't we discuss the parameters of DbiAcqPersistTableLock now?

DbiAcqPersistTableLock takes three parameters. They are described below:

Parameter Type Description
hDB Database Handle Handle to a valid database. Supply a valid TDatabase handle here, or you can do it the long way with BDE calls (I suggest you don't do this)


pszTableName PChar This is the fully qualified file name of a table (Path/Name). But for our purposes, we can submit any name. We just want to create the lock files.


pszDriverType PChar This is the name of the type of table driver to be used for creating the lock files. Since what we're doing is Paradox-specific, and for desktop databases, only Paradox is supported, the only driver name you can supply here is 'PARADOX'

Okay, you're now ready to make lock files. As you can see from the code, it's not a difficult thing to do at all. One last thing before I close this discussion. Notice that I make calls to DbiInit and DbiExit in the method. These intialize and close the BDE respectively. Even though they're not necessary to call when using DbiAcqPersistTableLock, I've gotten in the habit of enclosing any code that uses BDE calls within these two function calls just to ensure that I've got a connection to the database engine. It's just an insurance policy. Okay, that's it!