| Paradox BDE API: Reading and Updating the BDE Configuration File |
|
|
|
| Contributed by Rick Kelly | |
| 02 June 2003 | |
|
Everything you ever wanted to know about using the BDE's API to determine and set BDE configuration options!Paradox BDE API Reading and Updating the BDE Configuration File © 2003 Rick Kelly www.crooit.com Preface A library and example script of all OPAL methods presented is available here in Paradox 9 format. If you have another version, open the PW32BDE library first and save it before running the script. It is well known that an incorrectly configured BDE can be a source of problems, that together with Windows registry options for items such as opportunistic locking, can lead to application failure and/or table problems requiring repair. This article deals with using the BDE API exposed in idapi32.dll to interrogate the currently loaded configuration file and make changes under programmatic control. Normally, applications must include initialization and exit calls to BDE. Paradox itself is taking care of these details so our approach can concentrate on using the BDE API knowing we've got a working and validated BDE session. Be aware that it is up to each application to ensure that BDE options are updated correctly. The presented methods only include basic error checking. Paradox and other BDE applications will require a shutdown/restart before applied changes are used. For additional BDE information, see the following articles: BDE Configuration Suggestions by Liz Woodhouse FAQ:PdoxWin:Net File Rules:2000.01.18 FAQ:PdoxWin:Paradox & BDE Limitations:2002.07.18 General BDE Limits The following topics are presented in this article:
BDE and Win32 API Many functions use structures which are not directly supported in Paradox. Pascal Hutton pioneered the basic technique for using structures and you are encouraged to review his article at: http://www.thedbcommunity.com/code/structures.htm This is the basic technique used in this series in passing and retrieving structures when calling Win32 and BDE functions. The following Win32 API’s USES clauses are not addressed in this article. Uses "kernel32.dll"
GlobalAlloc(
wFlags cLong,
dwBytes cLong) cLong [stdcall]
GlobalFree(hMem cLong) cLong [stdcall]
GetStringSize(cString cLong) cLong [stdcall "lstrlenA"]
MoveFromMemory(
wDestination cPtr,
wSource cLong,
wLength cLong) cLong [stdcall "RtlMoveMemory"]
MoveToMemory(
wDestination cLong,
wSource cPtr,
wLength cLong) cLong [stdcall "RtlMoveMemory"]
GetModuleHandle(lpstrModule cPtr) cLong [stdcall "GetModuleHandleA"]
VerLanguageName(
wLang cLong,
szLang cPtr,
nSize cLong) cLong [stdcall "VerLanguageNameA"]
endUses
Uses "version.dll"
GetFileVersionInfoSize(
lptstrFilename cPtr,
lpdwHandle cPtr) cLong [stdcall "GetFileVersionInfoSizeA"]
GetFileVersionInfo(
lptstrFilename cPtr,
dwHandle cLong,
dwLength cLong,
lpData cLong) cLong [stdcall "GetFileVersionInfoA"]
VerQueryValue(
pBlock cLong,
lpSubBlock cPtr,
lpBuffer cPtr,
puLen cPtr) cLong [stdcall "VerQueryValueA"]
endUses
Our basic USES clause for the BDE API is:
Uses "idapi32.dll"
DbiGetErrorString(
lpError cLong,
lpDescription cLong) cLong [stdcall]
DbiGetSysVersion(lpVersion cLong) cLong [stdcall]
DbiGetSysInfo(lpSysInfo cLong) cLong [stdcall]
DbiGetSysVersion(lpVersion cLong) cLong [stdcall]
DbiGetSysInfo(lpSysInfo cLong) cLong [stdcall]
DbiGetSysConfig(lpSysConfig cLong) cLong [stdcall]
DbiOpenCfgInfoList(
hCfg cLong,
eOpenMode cLong,
eConfigMode cLong,
lpstrCfgPath cPtr,
lphCur cPtr) cLong [stdcall]
DbiSetToBegin(hCur cLong) cLong [stdcall]
DbiGetNextRecord(
hCur cLong,
eLock cLong,
lpRecBuf cLong,
lpRecProps cLong) cLong [stdcall]
DbiModifyRecord (
hCursor cLong,
pRecBuf cLong,
bFreeLock cLong) cLong [stdcall]
DbiCloseCursor(hCur cLong) cLong [stdcall]
endUses
Constants referenced throughout our examples are:
Const ; ; Milliseconds in one day ; cnOneDay = 86400000.0 ; ; BDE Error Codes ; cnDBIErrorNone = 0 cnDBIErrorEOF = 8706 ; ; BDE API Options ; cnDBIReadOnly = 0 cnDBIReadWrite = 1 cnDBINoLock = 0 cnDBIReadLock = 1 cnDBIWriteLock = 2 cnFreeLock = 1 ; ; BDE Structure Sizes ; cnSysInfo = 14 cnSysVersion = 12 cnSysConfig = 346 endConstGenerally, the BDE API return BDE error codes for a failure. The method DbiGetErrorString takes a BDE error code and returns its associated descriptive text. The following method is available when the descriptive text is needed. method GetBDEErrorMessage(liError LongInt) String
;
; Given an error code, get the description
;
var
stErrorMessage String
liMemory LongInt
liReturn LongInt
endVar
;
; Check for any of our own error codes first
;
switch
case liError = -1 :
stErrorMessage = "Unable to locate idapi32.dll"
otherwise :
liMemory = cmW32GlobalAlloc(256)
liReturn = DbiGetErrorString(
liError,
liMemory)
switch
case liReturn = cnDbiErrorNone :
stErrorMessage = cmW32MoveFixedStringFromMemory(
liMemory,
256)
otherwise :
stErrorMessage = "Unknown"
endSwitch
liReturn = cmW32GlobalFree(liMemory)
endSwitch
return "[" + strval(liError) + "]" + stErrorMessage
endMethod
Retrieval of Basic BDE Environmental Values The following BDE API’s are used to retrieve session statistics.
Type
BDESessionInfo =
Record
Idapi32DLLPath String
Idapi32DLLDate DateTime
Idapi32DLLVersion String
BDEVersion String
BDEClientVersion String
BDEDate DateTime
BufferSpace SmallInt
HeapSpace SmallInt
ActiveDrivers SmallInt
ActiveClients SmallInt
ActiveSessions SmallInt
ActiveDatabases SmallInt
ActiveCursors SmallInt
NetProtocol SmallInt
NetShare Logical
NetType String
NetUserName String
NetConfigFile String
NetLanguage String
endRecord
endType
The most reliable indicator of which BDE version is running is the idapi32.dll file. Generally, the timestamp indicates the version, i.e. 5.11 pm for version 5.11. A better way to check is to right click the idapi32.dll file, select Properties and view the Version tab. In our included method, we will first attempt to retrieve the version information from idapi32.dll, and if that fails, use the timestamp. The Win32 API calls and procedure for interrogating the idapi32.dll are fairly complicated. Developers of EXE/DLL file types have the option of including versioning using the MS standard methodology and multiple languages and code pages are supported. As I do not have availability to non-English BDE versions, it may be necessary to modify the code for the language and code page values. See comments in the PW32BDE library procedure cmGetFileVersionInfo for comments on how to check the available language and code pages if necessary.The basic steps involved are:
Proc cmGetFileVersionInfo(stFileName String) String
var
liHandle LongInt
liSize LongInt
liReturn LongInt
liPointer LongInt
liBufferSize LongInt
liBuffer LongInt
liString LongInt
siLanguage SmallInt
siCodePage SmallInt
stSubBlock String
stVersion String
endVar
liHandle = 0
stVersion = blank()
liSize = cmW32GetFileVersionInfoSize(stFileName,liHandle)
switch
case liSize < 1 :
otherwise :
;
; Allocate file info version structure
;
liPointer = cmW32GlobalAlloc(liSize)
liReturn = cmW32GetFileVersionInfo(
stFileName,
liHandle,
liSize,
liPointer)
switch
case liReturn = 1 :
;
; Retrieve the Language and CodePage values
;
liBuffer = 0
liBufferSize = 0
liReturn = cmW32VerQueryValue(
liPointer,
"\\VarFileInfo\\Translation",
liBuffer,
liBufferSize)
switch
case liReturn = 0 or liBufferSize = 0 :
siLanguage = 1033
siCodePage = 1252
otherwise :
siLanguage = 0
siCodePage = 0
siLanguage =
cmW32MoveSmallIntFromMemory(liBuffer)
siCodePage = cmW32MoveSmallIntFromMemory(liBuffer + 2)
endSwitch
;
; Build the SubBlock Key
;
stSubBlock =
cmGetFileInfoSubBlock(siLanguage,siCodePage)
;
; Allocate space for file info strings
;
liString = cmW32GlobalAlloc(100)
;
; Retrieve File Version
;
stVersion = cmGetFileInfoString(
liPointer,
liString,
stSubBlock,
"FileVersion")
;
; Free memory used for file info string
;
cmW32GlobalFree(liString)
endSwitch
;
; Free memory used for file info version structure
;
cmW32GlobalFree(liPointer)
endSwitch
return stVersion
endProc
Proc cmGetFileInfoSubBlock(
siLanguage SmallInt,
siCodePage SmallInt) String
;
; Build the SubBlock Key
;
var
stAny String
stSubBlock String
endVar
stAny = toHex(siLanguage)
stSubBlock = stAny.substr(7,4)
stAny = toHex(siCodePage)
return stSubBlock + stAny.substr(7,4)
endProc
Proc cmGetFileInfoString(
var liPointer LongInt,
var liString LongInt,
stSubBlock String,
stStringType String) String
;
; Retrieve file info strings from version block
; pointed to by liPointer
;
var
liReturn LongInt
liStringSize LongInt
stAny String
endVar
liStringSize = 0
stAny = blank()
liReturn = cmW32VerQueryValue(
liPointer,
"\\StringFileInfo\\" +
stSubBlock +
"\\" +
stStringType,
liString,
liStringSize)
switch
case liReturn = 0 or
liStringSize < 2 :
otherwise :
stAny = cmW32MoveStringFromMemory(liString)
endSwitch
return stAny
endProc
Proc cmW32GetFileVersionInfo(
stFileName String,
var liHandle LongInt,
var liSize LongInt,
var liBuffer LongInt) LongInt
;
; Retrieve file version info structure
;
return GetFileVersionInfo(
stFileName,
liHandle,
liSize,
liBuffer)
endProc
Proc cmW32GetFileVersionInfoSize(
stFileName String,
var liHandle LongInt) LongInt
return GetFileVersionInfoSize(stFileName,liHandle)
endProc
Proc cmW32GetModuleHandle(stModuleName String) LongInt
return GetModuleHandle(stModuleName)
endProc
Proc cmW32VerLanguageName(siLanguage LongInt) String
;
; Return name of given language id
;
var
stAny String
liReturn LongInt
endVar
stAny = space(256)
liReturn = VerLanguageName(
siLanguage,
stAny,
256)
return stAny.rTrim()
endProc
Proc cmW32VerQueryValue(
var liVersionBlock LongInt,
stSubBlock String,
var liBuffer LongInt,
var liBufferSize LongInt) LongInt
return VerQueryValue(
liVersionBlock,
stSubBlock,
liBuffer,
liBufferSize)
endProc
Now we are ready to search for the idapi32.dll date and version. The basic steps are:
Proc cmIdapi32Info(
var SessionInfo BDESessionInfo,
var liError LongInt) Logical
;
; Retrieve Idapi32.dll datetime stamp and version
;
var
loReturn Logical
fs FileSystem
endVar
;
; Locate Idapi32.dll using path stored in registry
;
SessionInfo.Idapi32DLLPath = getRegistryValue(
"software\\borland\\database engine",
"dllpath",
regKeyLocalMachine)
;
; See if we can extract the version directly from Idapi32.dll
;
SessionInfo.Idapi32DLLVersion =
cmGetFileVersionInfo(SessionInfo.Idapi32DLLPath + "\\idapi32.dll")
loReturn = fs.findFirst(SessionInfo.Idapi32DLLPath + "\\idapi32.dll")
switch
case loReturn = True :
SessionInfo.Idapi32DLLDate = fs.time()
;
; If version was not extracted earlier, use the timestamp as the version
;
switch
case SessionInfo.Idapi32DLLVersion.isBlank() = True :
SessionInfo.Idapi32DLLVersion =
format("W4.2",
((hour(SessionInfo.Idapi32DLLDate) * 100.0)
+ minute(SessionInfo.Idapi32DLLDate))
/ 100.0)
endSwitch
otherwise :
liError = -1
endSwitch
return loReturn
endProc
DbiGetSysVersionDbiGetSysVersion returns:
Proc cmDbiGetSysVersion(
var SessionInfo BDESessionInfo,
var liError LongInt) Logical
;
; Retrieve BDE version, date and time
;
var
liMemory LongInt
liReturn LongInt
daAny Date
endVar
liMemory = cmW32GlobalAlloc(cnSysVersion)
liError = DbiGetSysVersion(liMemory)
switch
case liError = cnDBIErrorNone :
SessionInfo.BDEVersion =
format("W4.2",cmW32MoveSmallIntFromMemory(liMemory) /
100.0)
SessionInfo.BDEClientVersion =
format("W4.2",cmW32MoveSmallIntFromMemory(liMemory + 2)
/ 100.0)
daAny = date(cmW32MoveLongIntFromMemory(liMemory + 4))
;
; BDE 5.2 returns a bogus year of 1901
;
switch
case year(daAny) < 1950 :
daAny = date(
month(daAny),
day(daAny),
year(daAny) + 100)
endSwitch
SessionInfo.BDEDate = datetime(number(daAny) * cnOneDay) +
cmW32MoveLongIntFromMemory(liMemory + 8)
endSwitch
liReturn = cmW32GlobalFree(liMemory)
return liError = cnDBIErrorNone
endProc
DbiGetSysInfoDbiGetSysInfo returns:
Proc cmDbiGetSysInfo(
var SessionInfo BDESessionInfo,
var liError LongInt) Logical
;
; Retrieve Session Statistics
;
var
liMemory LongInt
liReturn LongInt
endVar
liMemory = cmW32GlobalAlloc(cnSysInfo)
liError = DbiGetSysInfo(liMemory)
switch
case liError = cnDBIErrorNone :
SessionInfo.BufferSpace =
cmW32MoveSmallIntFromMemory(liMemory)
SessionInfo.HeapSpace =
cmW32MoveSmallIntFromMemory(liMemory + 2)
SessionInfo.ActiveDrivers =
cmW32MoveSmallIntFromMemory(liMemory + 4)
SessionInfo.ActiveClients =
cmW32MoveSmallIntFromMemory(liMemory + 6)
SessionInfo.ActiveSessions =
cmW32MoveSmallIntFromMemory(liMemory + 8)
SessionInfo.ActiveDatabases =
cmW32MoveSmallIntFromMemory(liMemory + 10)
SessionInfo.ActiveCursors =
cmW32MoveSmallIntFromMemory(liMemory + 12)
endSwitch
liReturn = cmW32GlobalFree(liMemory)
return liError = cnDBIErrorNone
endProc
DbiGetSysConfigDbiGetSysConfig returns:
Proc cmDbiGetSysConfig(
var SessionInfo BDESessionInfo,
var liError LongInt) Logical
;
; Retrieve Configuration
;
var
liMemory LongInt
liReturn LongInt
endVar
liMemory = cmW32GlobalAlloc(cnSysConfig)
liError = DbiGetSysConfig(liMemory)
switch
case liError = cnDBIErrorNone :
SessionInfo.NetProtocol =
cmW32MoveSmallIntFromMemory(liMemory + 2)
SessionInfo.NetShare =
iif(cmW32MoveSmallIntFromMemory(liMemory + 4) =
1,True,False)
SessionInfo.NetType =
cmW32MoveFixedStringFromMemory(liMemory + 6,32)
SessionInfo.NetUserName =
cmW32MoveFixedStringFromMemory(liMemory + 38,15)
SessionInfo.NetConfigFile =
cmW32MoveFixedStringFromMemory(liMemory + 53,260)
SessionInfo.NetLanguage =
cmW32MoveFixedStringFromMemory(liMemory + 314,32)
endSwitch
liReturn = cmW32GlobalFree(liMemory)
return liError = cnDBIErrorNone
endProc
Now that we have all the supporting routines for setting our BDESessionInfo record structure, let’s put it all together.
method GetBDESessionInfo(
var SessionInfo BDESessionInfo,
var liError LongInt) Logical
;
; Retrieve Session configuration and statistics
;
var
loReturn Logical
endVar
;
; Initialize variables
;
loReturn = False
liError = 0
SessionInfo.Idapi32DLLPath = blank()
SessionInfo.Idapi32DLLDate = blank()
SessionInfo.Idapi32DLLVersion = blank()
SessionInfo.BufferSpace = 0
SessionInfo.HeapSpace = 0
SessionInfo.ActiveDrivers = 0
SessionInfo.ActiveClients = 0
SessionInfo.ActiveSessions = 0
SessionInfo.ActiveDatabases = 0
SessionInfo.ActiveCursors = 0
SessionInfo.NetProtocol = 0
SessionInfo.NetShare = False
SessionInfo.NetType = blank()
SessionInfo.NetUserName = blank()
SessionInfo.NetConfigFile = blank()
SessionInfo.NetLanguage = blank()
;
; Retrieve session configurations
;
switch
;
; Idapi32.dll Info
;
case cmIdapi32Info(SessionInfo,liError) = False :
case cmDbiGetSysVersion(SessionInfo,liError) = False :
case cmDbiGetSysInfo(SessionInfo,liError) = False :
otherwise :
loReturn = cmDbiGetSysConfig(SessionInfo,liError)
endSwitch
return loReturn
endMethod
Retrieval of BDE Configuration Options Anyone who has used the BDE Configuration Editor has seen the many options supported in BDE configuration files. Generally, a subset of these are of concern to Paradox applications. Our approach will allow access to the full set of options and our examples will show access to the most common subset. The BDE configuration file is structured much like the Windows registry. In the Windows registry, there are several hives such as HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE. Think of the BDE configuration file as having one, unnamed hive. Access is by providing a key path which allows the retrieval of named values all which are of String Type. When we supply a path, it works like a filter for the associated cursor. Common paths of interest might be:
Type dyConfig = DynArray[] String endTypeOpening the BDE Configuration File DbiOpenConfigInfoList is the BDE API to open the configuration file. The following procedure supports this API. Proc cmOpenConfigFile(
stPath String,
liOpenMode LongInt,
liConfigMode LongInt,
var liCursor LongInt,
var liError LongInt) Logical
;
; Open BDE Configuration File
;
; Returns a cursor handle to an in-memory table
;
liError = DbiOpenCfgInfoList(
0,
liOpenMode,
liConfigMode,
stPath,
liCursor)
return liError = cnDbiErrorNone
endProc
liOpenMode is set for either read-only or write access. stPath is the access key or filter, and if successful, a cursor is returned in liCursor.Setting Cursor To Beginning After a successful open, the BDE API DbiSetToBegin positions the cursor to the beginning of our in-memory table. Proc cmSetToBegin( var liCursor LongInt, var liError LongInt) Logical ; ; Position cursor to beginning of table ; liError = DbiSetToBegin(liCursor) return liError = cnDbiErrorNone endProcScanning Our Cursor The BDE API DbiGetNextRecord will sequentially retrieve records using a supplied cursor. Optionally, we can also request a record lock. The record is returned in a record buffer referenced by liMemory. Proc cmGetNextRecord(
var liCursor LongInt,
var liMemory LongInt,
liLockOption LongInt,
var liError LongInt) Logical
;
; Retrieve Next Record
;
liError = DbiGetNextRecord(
liCursor,
liLockOption,
liMemory,
0)
return liError = cnDbiErrorNone
endProc
Closing Our CursorIf we have an open cursor, it is good practice to close it and release any associated resources. Proc cmCloseCursor(var liCursor LongInt) ; ; Close cursor and release resources ; var liReturn LongInt endVar liReturn = DbiCloseCursor(liCursor) endProcPutting it all together Now we are ready to retrieve BDE configuration values and build our returned DynArray. Note that the DynArray is not emptied first. This is to support a sequence of calls to different portions of the BDE configuration file and accumulate all values into a single DynArray. method ReadBDEConfigurationFile(
stPath String,
var dyFields dyConfig,
var liError LongInt) Logical
;
; Read BDE Configuration File
;
; Returns all the nodes in the configuration
; file accessible by stPath
;
;
var
liCursor LongInt
loReturn Logical
loScan Logical
liMemory LongInt
liReturn LongInt
endVar
loReturn = False
liCursor = 0
switch
case cmOpenConfigFile(
stPath,
cnDbiReadOnly,
0,
liCursor,
liError) = False :
case cmSetToBegin(
liCursor,
liError) = False :
otherwise :
liMemory = cmW32GlobalAlloc(1024)
loScan = True
while loScan = True
switch
case cmGetNextRecord(
liCursor,
liMemory,
cnDBINoLock,
liError) = True :
dyFields[cmW32MoveFixedStringFromMemory(liMemory,160)] =
cmW32MoveFixedStringFromMemory(liMemory + 162,160)
otherwise :
loScan = False
switch
case liError = cnDBIErrorEOF :
loReturn = True
liError = cnDbiErrorNone
endSwitch
endSwitch
endWhile
liReturn = cmW32GlobalFree(liMemory)
endSwitch
;
; Close cursor, if opened
;
switch
case liCursor <> 0 :
cmCloseCursor(liCursor)
endSwitch
return loReturn
endMethod
Updating of BDE Configuration Options Review the previous section on retrieval of BDE configuration options. For updating, we need only introduce one additional BDE API. DbiModifyRecord is used to update a record previously retrieved by DbiGetNextRecord. Proc cmModifyRecord(
var liCursor LongInt,
var liMemory LongInt,
liFreeLock LongInt,
var liError LongInt) Logical
;
; Update Record
;
liError = DbiModifyRecord(
liCursor,
liMemory,
liFreeLock)
return liError = cnDbiErrorNone
endProc
In the cursor scan process, we will be retrieving records with a write lock and releasing the lock after the update. If the update is successful, the value name is removed from the passed DynArray. This will allow the caller to examine the DynArray upon successful return and see if there were values that could not be found.
method UpdateBDEConfigurationFile(
stPath String,
var dyFields dyConfig,
var liError LongInt) Logical
;
; Read BDE Configuration File
;
; Updates all the nodes in the configuration
; file accessible by stPath and referenced
; in dyFields
;
var
liCursor LongInt
loReturn Logical
loScan Logical
liMemory LongInt
liReturn LongInt
stNodeName String
stNodeValue String
liNodeSize LongInt
endVar
loReturn = False
liCursor = 0
switch
case cmOpenConfigFile(
stPath,
cnDBIReadWrite,
0,
liCursor,
liError) = False :
case cmSetToBegin(
liCursor,
liError) = False :
otherwise :
liMemory = cmW32GlobalAlloc(1024)
loScan = True
while loScan = True
switch
case cmGetNextRecord(
liCursor,
liMemory,
cnDBIWriteLock,
liError) = True :
stNodeName =
cmW32MoveFixedStringFromMemory(liMemory,160)
switch
;
; If item is found in dyFields, update BDE configuration
;
case dyFields.contains(stNodeName) = True :
stNodeValue = dyFields[stNodeName]
liNodeSize = stNodeValue.size() + 1
MoveToMemory(
liMemory + 162,
stNodeValue,
liNodeSize)
loScan = cmModifyRecord(
liCursor,
liMemory,
cnFreeLock,
liError)
;
; Remove item. This is so caller can check if there were
; items not found.
;
dyFields.removeItem(stNodeName)
endSwitch
otherwise :
loScan = False
switch
case liError = cnDBIErrorEOF :
loReturn = True
liError = cnDbiErrorNone
endSwitch
endSwitch
endWhile
liReturn = cmW32GlobalFree(liMemory)
endSwitch
;
; Close cursor, if opened
;
switch
case liCursor <> 0 :
cmCloseCursor(liCursor)
endSwitch
return loReturn
endMethod
Acknowledgments Rodney Wise for his feedback, enthusiasm and tireless effort in testing. Conclusion We now have methods that support interrogation and updating of many BDE session and configuration options that allow Paradox applications to better manage and control their runtime environment. |
| < Prev | Next > |
|---|





