How to change NET DIR programmatically so it persists

By: Steven Randolph

Abstract: Writing values into the BDE configuration file requires only a few function calls.

How to change NET DIR programmatically so it persists

Writing values into the BDE configuration file requires only a few function calls.

by Steven Randolph

Programs accessing Paradox tables must also access a special control file named PDOXUSRS.NET. When multiple programs, whether running on one computer or on multiple computers, access the same Paradox table(s), each program must also access the same PDOXUSRS.NET file. Therefore, if multiple computers are running the programs, the PDOXUSRS.NET file must be located in a shared directory. Usually, the location of this file is configured on each computer by running the BDE configuration utility program, BDEADMIN.EXE. The NET DIR parameter indicates where the PDOXUSRS.NET file is located. That NET DIR parameter is associated with the Paradox driver generally, not with any particular alias.

THE PROBLEM AND THE GOAL

I recently inherited the maintenance of a production system consisting of several client programs running on multiple computers. Each program accesses Paradox tables through multiple aliases. Each alias "points" to a different directory on a host computer. Each program also reads and writes flat files in a large number of directories on one of two hosts. One of the first improvements that I determined to make was to set up a test environment, which my predecessor had never done. I wanted a single host computer for the test environment to have its own tables, its own set of directories, and its own PDOXUSRS.NET file. In other words, any client computer designated for testing should be isolated from the production host environment. I also wanted to make the minimum of changes to the existing production programs to allow for this.

While it might seem that the entire task could be accomplished by changing the drive mappings on the test computers, which can be done programmatically, in my case that was not practical. Some of the same drive mappings are used for other purposes in our environment. Besides, this would have required me to create the same rather arcane relative directory structure on the test host as exists on the two production hosts, and I did not wish to do that.

So I decided to create a utility program that would accomplish several tasks at once. In this program, the user could click a button labeled "Configure for Test" or one labeled "Configure for Production" and the configuration would be made immediately on that computer. It would create or modify registry entries specifying the various directories to access for the flat files. This part was easy to program, using the TRegistry component. The program also needed to configure the several Paradox aliases to point to either test or production directories. This was also no problem; I merely used the DbiAddAlias and DbiDeleteAlias functions, both of which are well documented. The only variable parameter for the creation of the Paradox aliases was the directory location, and everything else was left at default values. Setting the last parameter in the DbiAddAlias call to "true" caused the alias to be recorded permanently in the BDE configuration file for that computer. However, doing the same for the NET DIR value was not so easy!

There is a documented technique for changing the NET DIR directory programmatically. It is documented in Borlands TI-15230. This technique uses the DbiSetProp BDE function. Unfortunately, DbiSetProp changes the NET DIR directory for the current session only! That is, it does not cause the new value for NET DIR to be written into the BDE configuration file. It took quite a bit of searching and some experimentation to determine how to do that. Finally I figured out that the code in the following block does the trick.

THE SOLUTION

First is the C++ Builder code. You might want to do the error trapping a little differently, or put in some messages, in a real program. This is just an example of the basic technique.

  if(DbiInit(NULL) == DBIERR_NONE)
  {
    hDBICur hCur = NULL;
    if(DbiOpenCfgInfoList(NULL, dbiREADWRITE, cfgPersistent,
            "\\DRIVERS\\PARADOX\\INIT", hCur) == DBIERR_NONE)
    {
      if(DbiSetToBegin(hCur) == DBIERR_NONE)
      {
        CFGDesc Config;
        for(;;)   // for ever
        {
          if(DbiGetNextRecord(hCur, dbiWRITELOCK, &Config, NULL)
                 != DBIERR_NONE) break;
          if(strcmpi(Config.szNodeName, "NET DIR") == 0)
          {
            strcpy(Config.szValue, "servervolumemydir");
            DbiModifyRecord(hCur, &Config, true);
            break;
          }
        }
      }
    }
    DbiExit();
  }
The Delphi version of this process is similar.
var
  hCur : hDBICur;
  Config : CFGDesc;
  ContinueIt: boolean;

...

  if DbiInit(nil) = DBIERR_NONE then
  begin
    hCur := nil;
    if DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPersistent,
        '\\DRIVERS\\PARADOX\\INIT', hCur) = DBIERR_NONE then
    begin
      if DbiSetToBegin(hCur) = DBIERR_NONE then
      begin
        ContinueIt := true;
        while ContinueIt do
        begin
          if(DbiGetNextRecord(hCur, dbiWRITELOCK, @Config, nil)
                 <> DBIERR_NONE) then
            ContinueIt := false
          else
            if StrIComp(Config.szNodeName, 'NET DIR') = 0 then
            begin
              StrCopy(Config.szValue, 'servervolumemydir');
              DbiModifyRecord(hCur, @Config, true);
              ContinueIt := false
            end;
        end;
      end;
    end;
    DbiExit();
  end;
If you use this code, you would want to substitute your own value for the path name to be copied into Config.szValue, of course. In my real program, the value was supplied in a string variable, the contents being determined by whether the user had clicked the "test" button or the "production" button. The value you submit can contain a mapped drive letter, if you are quite sure those mappings will never change; but I always prefer to use the universal naming convention.

CAVEATS

My experimentation suggests that it is not strictly necessary to close other BDE programs prior to running this code. Just the same, it would probably be a good idea to do so. In any case, a BDE program will not take notice of a changed value in the BDE configuration file until it is started subsequent to the change. You may also notice this while debugging your utility program in the IDE, if you happen to change the value and then try to re-read it without calling DbiExit and DbiInit again. (In the example code, this should not be a problem because DbiExit is executed right after the change is written.)

Another important consideration is to make sure that the path you submit to be the new NET DIR actually exists. The BDE will, at runtime, create the PDOXUSRS.NET file if it does not already exist in the NET DIR directory; but the BDE will not create the NET DIR directory for you. If this happens, you will get a runtime error "Network Initialization Failed" when your program tries to initialize or access a Paradox object.

You should also remember that if you have programs that change their own NET DIR locations at runtime, using either the DbiSetProp function or the NetFileDir property of a TSession component, this will override the NET DIR value in the configuration file. All of the programs I inherited just use whatever value the configuration file supplies.

Finally, you should note that the same technique could be used to change other parameters for the Paradox driver, or many parameters for other drivers. You would have to be very careful about which values you supply, however. Changing the NET DIR directory is relatively safe.


Server Response from: ETNASC03