Search code examples
delphibde

How to find out the BDE's shared memory area's actual location and size programmatically?


The Borland Database Engine uses a shared memory area that has to be mapped in the same address in all BDE application processes concurrently running in the same Windows workstation. The area's location and size are guided by two settings called SHAREDMEMLOCATION and SHAREDMEMSIZE. Particularly the location setting only serves as a starting point, and the actual location might end up being something completely different. I'm not sure about the size.

Copies of the settings seem to be stored in several places. Each of them may contain different values, and particularly what comes to the location, none of which might be the actual value being used by a running BDE application.

  1. A workstation-wide BDE config file, for example C:\Program Files\Common Files\Borland Shared\BDE\idapi32.cfg (which is what the BDE Administrator application uses if it's "Run as Administrator")
  2. A user-specific file, for example C:\Users\user-name\AppData\Local\Temp\_ISTMP1.DIR\_ISTMP0.DIR\idapi32.cfg (An installer had apparently placed it in an InstallShield temporary folder, but anyway that's what BDE Administrator uses if it's started by a normal user)
  3. The Windows registry, HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Database Engine\Settings\SYSTEM\INIT,
  4. ... or its virtualized versions, VirtualStore (and "Wow6432Node" if it's a 64 bit Windows)

It seems that at least if the registry values exist, in practice that's the only place the BDE will actually look, but the BDE Administrator utility fails to update the registry, which seems to be a common pitfall for BDE users.

There happens to be a bunch of legacy BDE applications I need to support, and the applications are run in Windows systems that are not in my control. One problem is that the SHAREDMEMSIZE is too small, and it causes "$2501" BDE errors, and I would like to add an automatic check for the actually used effective values for SHAREDMEMSIZE and SHAREDMEMLOCATION, so an application could self-diagnose the problem.

What makes SHAREDMEMLOCATION "interesting" is that the BDE does not necessarily create its shared memory area at that location at all. The process goes something like this:

  • Case A) If I am the first BDE application and the shared memory area has not been allocated:
    • Try to allocate SHAREDMEMSIZE bytes of memory at SHAREDMEMLOCATION.
    • If that space doesn't happen to be free for me right now, then look at ... any other place, using some sort of algorithm, and put it wherever there is a free contiguous address space range of SHAREDMEMSIZE bytes! Whoah.
  • Case B) If I am not the first running BDE application process, and the shared memory area already exists on this workstation
    • Try to map that area (wherever it is)
    • If I cannot map it to THIS process, because there's something there already (code or data or anything), then give the "$210D" error, "Shared memory conflict"

Only after the last BDE application process has been closed, does the shared memory area get to be placed again. (If this seems interesting, you can observe how it works with the Sysinternals VMMap tool)

As you can imagine, where and if the shared memory area fits in case A, and if it happens to suit the next application process in case B, can be slightly random. For example, if you change the size parameter, it can result in "fixing" the $210D error, just because a smaller or larger block happened to fit somewhere else that happened to suit some combination of applications (started in some order). And it doesn't really help that people try to fix it with the BDE Administrator program, which looks very convincing to an unsuspecting user or administrator, but doesn't actually change the registry settings that would have an effect on what actually happens. (this might be a bug, but so what)

So my question is, how can I programmatically check what size and location the BDE is actually using? I know how to read the configuration files, and I can query the BDE itself, and it reports its configuration based on what there is in the files. Unfortunately it does not necessarily act upon the settings in the files at all, so whatever values are reported, may not be the effective settings. I can read the registry values, but even that might not tell where the shared memory area actually is.

I've looked at the functions exported by idapi32.dll, but couldn't find anything that might report the actual location and size of the shared memory area. I even tried to "de-compile" BDE.DCU, but it seems to just use the idapi DLL functions and sits even further away from the dirty internal details.

I'm thinking, I could try and scan the BDE's internal memory area and try to locate instances of the address, but I would think that certainly there has to be a function or something for this?

The best kludge I have so far for working around the BDE errors is that my application (every one of them actually) changes the registry settings very early on in the unit initialization chain. Sure, the changes will only go under the VirtualStore thingy, but at least the BDE seems to read the values from there. But if there are $2501 (insufficient memory) or $210D (location clash) errors, it would be nice to be able to tell if the actual location and size of the shared memory area were different from the settings my program tried to use.

Edit: if everyone could please try and refrain from explaining things like what color the sky usually is, or what kind of database options there are for Delphi applications. Thanks. :)


Solution

  • As it seemed that I won't be getting any quick StackOverflow answers, I reverse-engineered how it works. I looked at the function names in idapi32.dll and noticed a few interesting ones, particularly OsSetSharedPtr and OsGetSharedPtr. With some help from Delphi's Modules list and CPU window, I put breakpoints on the function entry points and looked at the parameters and return values when BDE initializes and de-initializes itself. Here's some example code, how to call the function idapi32.OsGetSharedPtr()

    type PCardinal = ^Cardinal;
    
    type
      TOsGetSharedPtrFunc = function (Par1 : Cardinal; Par2 : PCardinal) : Cardinal; stdcall;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      OsGetSharedPtrFunc : TOsGetSharedPtrFunc;
      base, size : Cardinal;
      lib : HMODULE;
    begin
      lib := LoadLibrary('idapi32.dll');
      if lib = 0 then
        ShowMessage('Could not LoadLibrary().')
      else
        try
          OsGetSharedPtrFunc := GetProcAddress(lib, 'OsGetSharedPtr');
          if @OsGetSharedPtrFunc <> nil then
          begin
            OsGetSharedPtrFunc(9, @base);
            OsGetSharedPtrFunc($A, @size);
            ShowMessageFmt('%x %x', [base, size]);
          end
          else
            ShowMessage('Could not GetProcAddress().');
        finally
          FreeLibrary(lib);
        end;
    end;
    

    With number 9 as the first parameter, you can ask what the shared memory area's base location is, and with number 10, you get its size. The function return value seems to be the same address, my example just doesn't use the return value.

    I do not take any responsibility of the correctness of this information. If you use this reverse-engineered kludge/hack, and something bad happens, blame yourself.