![]() |
![]() |
|
![]() |
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"] endUsesOur 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] endUsesConstants 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 endTypeThe 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) endProcNow 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 endProcDbiGetSysVersion DbiGetSysVersion 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 endProcDbiGetSysInfo DbiGetSysInfo 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 endProcDbiGetSysConfig DbiGetSysConfig 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 endProcNow 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 endProcliOpenMode 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 endProcClosing Our Cursor If 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 endProcIn 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. Discussion of this article |
![]() Feedback | Paradox Day | Who Uses Paradox | I Use Paradox | Downloads ![]() |
|
![]() The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation. Paradox is a registered trademark of Corel Corporation. ![]() |
|
![]() Modified: 03 Jun 2003 Terms of Use / Legal Disclaimer ![]() |
![]() Copyright © 2001- 2003 Paradox Community. All rights reserved. Company and product names are trademarks or registered trademarks of their respective companies. Authors hold the copyrights to their own works. Please contact the author of any article for details. ![]() |
![]() |
|