PFXplus 4.40 is Here! | PFX C-lib as a TDataSet in Delphi | Sizing your screens | Utility program for Y2K data | Technical tip on data entry | Converting character-based help to Windows format | Powerflex news
Powerflex Corporation has this week released PFXplus version 4.40. This release maintains our commitment to continuously improving our products, adding new features and correcting any notified problems.
Two of the top features in this release are actually fixes for problems in Windows 95. Microsoft may be too busy, but we listen to our customers and you reap the benefits.
First, this release fixes the notorious "OpLocks" problem. Opportunistic locking ("OpLocks") is a performance enhancement in Windows, intended to make single-user applications run faster. It does that, but unfortunately it is faulty in all versions of Windows 95/98, causing data corruption in multi-user applications such as PFXplus. We have long recommended that it be disabled, unless you are using exclusively Windows/NT 4.0 Workstations.
Now using PFXplus 4.40, Windows/NT Server 4.0 SP3 and Windows 95C workstations, you can at last leave OpLocks enabled and reap the performance benefit. See our Windows Multiuser FAQ page for what we recommend.
The second fix is for the renowned Windows 95/98 console problem. On Windows 95/98 (but not Windows/NT) using PFLT to run console-mode programs, the CapsLock key works incorrectly, causing problems with Escape and number keys. PFXplus 4.40 fixes all that!
There are lots of new features too, including a picture control, module version control, support for Microsoft SQL version 7 and more. Here is a partial list.
Runtimes included with PFXplus version 4.40
32-bit Windows graphical runtime, runs character-mode and graphical-mode programs under Windows 95/98 and NT.
32-bit Windows Console Mode runtime, runs character-mode programs in a Console under Windows 95/98 and NT.
32-bit MS-DOS Extended runtime, runs character-mode programs in a DOS box under Windows 95/98 and NT.
PFX C-lib and PFXsort version 4.40, which support the new database features in PFXplus version 4.40, have also been released
For further details contact Powerflex Corporation or your local dealer.
I was a developer of DOS applications with Dataflex for several years. When I began writing applications for Windows, I didn't want to do so from scratch using a new database. I wanted to access proven and stable databases from my new Windows applications.
The PFX C-lib DLL solved that problem. It enabled me to get read and write access to the databases with Delphi even under Windows/NT. But I was missing the standard DB connectivity of the classic Delphi database components, which really cut development time dramatically.
So I started to write my own implementation of the DLL in Delphi. While this was nearly impossible with Delphi 2, version 3 provides a new VCL structure that makes creating custom datasets pretty easy, and Delphi 4 continues this way. The challenge was to get exactly the same functionality as with a TTable and therefore to be able to connect my databases to any Delphi data-aware control.
The (all but) finished component is available as freeware with source on the EC Software Web site http://www.ec-software.com/powerflex/.
Text conventions used in this article:
|Bold Methods||Key methods of TDataset, overwritten by our descendant|
|Bold italic methods||External calls to the PFX C-lib|
|PclMethods||Calls to the PFX C-lib are highlighted in the code examples|
You won't find any useful documentation about TDatasets in the Delphi help. I got this knowledge from investigating the VCL source, comparing the TDataset with its successor, the TBDEDataset, and guessing what's going on.
So if you're missing something, please forgive me. If you have implemented PFX C-lib before, you may be used to having one record buffer provided by the DLL through a pointer after a find was executed. You can read field values from this buffer and write them back again. When you execute a save command, the data is stored on disk. You never have to deal with two records at a time. Please forget this model.
The TDataset implements its own (multi) record buffering and we have to adopt this strictly if we want to stay compatible with any DB control that uses a TDatasource.
Figure 1: Relationship between a TDBGrid, a TDataSource and a TDataset
To understand how it works, imagine a DBGrid that displays 3 records (rows) of a table, starting with the very first record. The grid is connected to the data using a TDatasource ("DSRC") which is connected to our TDataset ("DS").
The grid now requests 3 record buffers from the DSRC, telling it to start with the very first one. The DSRC re-directs that request to the DS by calling certain functions repeatedly. You're dealing, in fact, with 3 record buffers simultaneously. Fortunately, users can edit only one record at a time and most of the buffer management is already implemented in the TDataset class, at least as abstract functions. Our descendant has to provide real database access by overwriting the abstract functions, of course.
What are these buffers all about and how do we fill them? The record buffer is a zero-terminated string, in other words a PChar. Do not mistake this buffer for the record buffer of the DLL. Our record buffer looks pretty much the same as the one you get by calling the PclGetRecPtr function. The field lengths are different, however, and the buffer is terminated by a bookmark that keeps track of the underlying database record.
Figure 2 explains the differences. Let's say we have a database called "employee.dat", which defines a couple of fields (more than five). If we open the table with our DS, we only want to display the first 5 fields; the rest are omitted. The DS will allocate memory for only 5 fields plus a bookmark. While the string fields require the same size as in the original record buffer, the date field "EntryDate", which takes 3 bytes in the physical table, now requires 8 bytes (TDateFields rely on a TDateTime value that is by definition a double).
Figure 2: Raw Powerflex record buffer, raw dataset record buffer and accessible field objects.
Before we can calculate the record buffer and its size, we have to know how many fields are required, and the data type and size of the fields. We also have to give them names.
One step back, please - our table is not even opened yet. First, the DSRC tells the DS to open the table by calling the InternalOpen method. Once we have opened the table successfully, we must initialize the field definitions. Then we can calculate the record buffer size. The following code examples are shortened, just to illustrate what happens.
procedure TPFXDataset.InternalOpen; var I: integer; begin fIsTableOpen := PclOpen(dfh,PChar(fTableName), 0) = 0; if not fIsTableOpen then DatabaseError('Cannot open table') else begin InternalInitFieldDefs; //see below //DefaultFields= no design time fields //CreateFields must create a list of //TField objects from pyhs. FieldDefs if DefaultFields then CreateFields; BindFields(True); //inherited function, binds //design time fields //calculate the record buffer size: fRecordSize := 0; for I := 0 to Fieldcount-1 do Inc(fRecordSize, fields[i].datasize); fRecordBufferSize := fRecordSize + sizeof(TRecInfo); end; end; procedure TPFXDataset.InternalInitFieldDefs; var ... begin PclFileDef(dfh, ftype, fcursiz, fMaxRecSize, fnfld, freclen, fflags, fctable); for I := 1 to fnfld do begin PclFieldDef(dfh, i, fdtyp, fdlen, fdpos, fddpl, fdidx, fdrfil,fdrele); tSize := 0; case fdtyp of 0: begin //ASC ttype := ftString; tSize := fdLen; end; 1: if fddpl = 0 then ttype := ftInteger else ttype := ftFloat; //NUM 2: ttype := ftDate; //DATE else ttype := ftUnknown; end; //You can read the field names from a //TAG file as well, if the TAG is //supported at design time and runtime: TFieldDef.Create(FieldDefs, 'Field_'+inttostr(I), tType, tSize, False, I+1); end; end;
Extract 1 (above): InternalOpen Method
So far so simple; our table is opened. We have created a list of TField objects and we know how large every record buffer has to be. Let's get back to the example shown in Figure 1. What happens if a DBGrid wants to display a couple of records? A DBGrid (and other DB components) always delegates its data requests to a datasource which re-directs almost everything to our dataset. The DSRC will call the InternalFirst method of our DS first. Our DS implementation will call a PclClear to empty the DLL record buffer so that the DLL finds the very first record on the next PclFind command.
procedure TPFXDataset.InternalFirst; begin PclClear(dfh); fCurrentRecord := 0; end;
After that, the DS will be forced to allocate a record buffer (AllocRecordBuffer) for each record that has to be displayed. By the way, if a record buffer is no longer required, the DSRC tells us to dispose the memory by calling our FreeRecordBuffer method.
function TPFXDataset.AllocRecordBuffer: PChar; begin Result := StrAlloc(fRecordBuffersize); end;
The result is a list of buffers that are filled in the GetRecord method. TGetMode defines how to navigate. The DS, again, is responsible to return either valid data (by copying it to the given buffer) or a find error. How the data is found is of no interest for the DSRC; it just wants results. We will execute a PclFind to get the next or the prior record. The GetRecord method is in fact the only event where we navigate through our database (except the first/last methods and when we want to re-find a bookmark).
function TPFXDataset.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; begin result := grOk; if GetRecordCount < 1 then Result := grEOF else case GetMode of gmCurrent: if fCurrentRecord < 1 then Result := grError; gmNext: if PclFind(dfh, GT, fIndexNumber) = 0 then result := grEOF; gmPrior: if PclFind(dfh, LT, fIndexNumber) = 0 then result := grBOF; end; fCurrentRecord := InternalGetRecnum; //reads physical recnum if Result = grOK then //fill record data area of buffer begin PFReadRecord(Buffer, fCurrentRecord); //actually reads the //record buffer ClearCalcFields(Buffer); //inherited GetCalcFields(Buffer); //inherited with PRecInfo(Buffer + fRecordInfoOffset)^ do begin //set buffer //bookmark flag BookmarkFlag := bfCurrent; Bookmark := fCurrentRecord; end; end else if (Result = grError) and DoCheck then raise ePFXDatasetError.Create ('Invalid record'); end;
Extract 2 (above): GetRecord Method
How exactly the record buffer of our DS is filled is a different story. Briefly, we must read each physical Powerflex database field, convert the content to a native Delphi value (pchar, double, integer) and copy the raw data into the record buffer of the DS. This is a bunch of functions dealing with pointers and buffers.
Well, let's assume that it is filled correctly. There are (despite the fact that we have not saved a record yet) a couple of important methods that have to be implemented to finish our working dataset. The most important of them are the internal buffer navigation methods. InternalSetToRecord is the key method that is always called when the "active" buffer changes. It gives us the requested buffer and we have to synchronize our physical database record in InternalGotoBookmark. Do you remember? We are working on multiple records simultaneously. Each record buffer is terminated by a bookmark and that bookmark contains our physical recnum. But we also need InternalGotoBookmark to find a record manually via a bookmark. Many DB components use bookmarks to navigate — to remember a previously selected record, for example.
procedure TPFXDataset.InternalSetToRecord(Buffer: PChar); var iBookmark: Integer; begin iBookmark := PRecInfo(Buffer + FRecordInfoOffset).Bookmark; InternalGotoBookmark(@iBookmark); end; procedure TPFXDataset.InternalGotoBookmark(Bookmark: Pointer); var iBookmark: Integer; begin iBookmark := PInteger(Bookmark)^; if (iBookmark > 0) then begin pclclear(dfh); PclPutField(dfh, 0, pchar(inttostr(iBookmark))); if PclFind(dfh, EQ, 0) = 0 then fCurrentRecord := iBookmark; end; end; function TPFXDataset.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; begin Result := PRecInfo(Buffer + FRecordInfoOffset).BookmarkFlag; end; procedure TPFXDataset.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); begin PRecInfo(Buffer + FRecordInfoOffset).BookmarkFlag := Value; end; procedure TPFXDataset.GetBookmarkData(Buffer: PChar; Data: Pointer); begin PInteger(Data)^ := PRecInfo(Buffer + FRecordInfoOffset).Bookmark; end; procedure TPFXDataset.SetBookmarkData(Buffer: PChar; Data: Pointer); begin PRecInfo(Buffer + FRecordInfoOffset).Bookmark := PInteger(Data)^; end;
Are we still missing anything? Yes, we are. It is that function that moves our record buffer(s) field by field to the buffer of the accompanying TField objects of our DS. Not unimportant, indeed. It is again a function defined as virtual in the TDataset base class and we must overwrite it. The function is GetFieldData. It simply copies the buffer of the active record to the value buffer of the TField object itself. TField then knows how to handle its buffer if we have filled it properly. The code example below assumes that we have an array for the offset of each field data in the record buffer (the offset array was created when we have opened the table). Otherwise we had to calculate the offset by adding field length after field length each time a TField object requests a data update.
function TPFTable.GetFieldData(Field: TField; Buffer: Pointer): Boolean; var Ptr: Pointer; RecBuf: PChar; I, Fieldoffset: LongInt; begin Result := False; if not GetActiveRecBuf(RecBuf) then Exit; if not IsEmpty and (Field.FieldNo > 0) then begin FieldOffset := GetFieldOffset[Field.fieldno-1]; Ptr := RecBuf; //ActiveBuffer; result := true; if Assigned(Buffer) then begin if (field.datatype in [ftDate,ftDateTime]) then begin Move(pBufArray(Ptr)^[FieldOffset], I, sizeof(i)); result := I <> 0; //return blank for empty date end else Move (pBufArray(Ptr)^[Fieldoffset], Buffer^, Field.DataSize) end else result := false; end; end;
To sum up, it is not possible to explain a complete dataset implementation on a single page. Too much stuff has been skipped in this short introduction. How are the record buffers filled, how do we write back changed data, how do we save a record, and how do we navigate through a table manually?
A working example component with source can be retrieved from the EC Software Web site http://www.ec-software.com/powerflex/.
Those who are running character-mode programs using PFLN will be familiar with the dilemma of being unable to run full size screens. As PFLN is a graphical, native 32-bit application, it runs a special character mode screen which looks like a DOS box. Being graphical, this character mode screen is unable to run full-screen.
The main focus of this article is to outline some techniques that can be used to control the size of the character mode screen when running C-mode programs with PFLN in a Windows environment. Note that this is not an issue when PFLN is used to run GUI programs.
The character mode screen size and appearance can be controlled by setting the configuration item sTermType=WIN:0,50,80 in the [PFLN] section of the configuration file PFX.INI.
Setting sTermType to WIN:0,50,80 sets the Windows driver mode to 0, the number of rows to 50 and the number of columns to 80. Varying screen sizes can be achieved by experimenting with these values.
This method of screen sizing works in conjunction with sConsoleFont, which sets the font name and size, to achieve the desired screen appearance.
When running PFLN, the Font properties, which would normally be available in a true DOS box, must be set in the configuration file PFX.INI instead.
The configuration item sConsoleFont is set in the [PFL] section to modify the font size, which will also have an impact on the screen size. For example:
sets font width to 7, height to 12. For a 800x600 desktop resolution sConsoleFont may be set to
The following examples demonstrate the difference in screen size.
sConsoleFont is set to Andale Mono
sConsoleFont is not set (default)
Some useful monospace fonts that sConsoleFont can be set to include Andale Mono (formerly Monotype.com) and a handful of others such as Lucida Console, Terminal, System and Courier.
These fonts may already be installed or they can be downloaded from the Web site http://www.microsoft.com/typography/fonts/.
Another factor to be considered in relation to screen size is the screen resolution. Of course each user has their own preference so it is not possible to design your screens to cater to each different screen resolution. We recommend therefore, that screens be developed for a 800x600 pixel resolution. Where a screen does not fit onto a 800x600 desktop, it may need to be broken down by using subscreens or perhaps by a tab control if it is a graphical-mode program.
The Windows Console Mode runtime, PFLT, is a native Win32 application which starts off in a console much like the old DOS screen. PFLT can run a full size console. The screen size can be controlled by setting the Font and Layout properties, situated at the top left corner of the console. PFLT is used to run character-mode programs under Windows. Because PFLT is a Windows application, printing is performed through Windows but being backwards compatible with the earlier DOS runtimes such as PFL3. It also has the ability to print via DOS.
It is possible to maximise and minimise your Windows dialogs from within the source code. The following code extract shows how to maximise and restore a Windows dialog.
//These values have been taken from //Windows header file WINRESRC.H #replace WM_SYSCOMMAND $00112 #replace SC_MAXIMIZE $0F030 #replace SC_RESTORE $0F120 Found = winSendMessage(ff\handle(),; WM_SYSCOMMAND,SC_RESTORE,0) Found = winSendMessage(ff\handle(),; WM_SYSCOMMAND,SC_MAXIMIZE,0)
Please note that dictating the dialog size is not recommended as it is outside standard Windows behaviour.
With less than six months to go before we reach the year 2000, it would be reasonable to assume that all of you have already made your PFXplus data and programs year 2000 compliant, right? No? Well before you start pushing the panic button, here is a program that should, at the very least, get you started.
PFY2KFIX.PFX is a utility program that has been written to convert non-Y2K compliant dates in PFXplus data files, to Y2K-compliant dates.
It will optionally process every file in the Filelist and search through all date fields. Where necessary (and where possible) it will convert low-range date values (dd/mm/yy) to high-range date values (dd/mm/19yy).
In each case, whether date low-range values are converted, nulled or ignored (as per the selected options), all actions are recorded in a log file.
The program provides the option to report on the data only, without modifying it. This allows you to check whether the data needs to be massaged and what percentage of dates are out of range before any data is converted.
The source code for this handy utility is available by e-mail. Simply send your request to firstname.lastname@example.org. Please note that this program will not be supported.
This program is provided to you free of charge by Powerflex Corporation. It’s all part of the service!
If you enter the letter "t" + X , where + is a ± operand and X is any integer, in a PFXplus date window, the resulting date will be the current date with X days added to it or subtracted from it.
For example if today’s date is June 13 1999
t_________ 13/06/1999 t-14______ 31/05/1999 t+365_____ 12/06/2000
Like many other Powerflex developers, we faced the challenge of converting our existing character based apps to a Windows GUI format. Because we have many large UNIX sites, we had the added requirement of maintaining compatibility with our character-based version – the thought of maintaining two versions of our software was scary – and probably impossible, given our present staffing levels. A big question was how to provide GUI help without maintaining a separate .HLP file.
This article explains the approach we took to this problem. We undertook some initial investigation into .HLP files and discovered that they begin life as a 'rich text format' (.RTF) document and are subsequently fed into the Microsoft help compiler (available free) to produce a .HLP file (and possibly a contents file .CNT). We therefore thought it possible to somehow extract our character-based help from our existing programs, put it into RTF format and 'bingo' we would have Windows help! The theory was good but its implementation took quite a bit of work! This is what we did.
The best way to illustrate this is to show one of our 'converted' character-help screens. Here is one of our existing character-help screens as an example:
/Help1_1 'Screen 1' This program allows the creation, maintenance, and deletion of creditors. For users of Landed Costs, a special note has been included at the end of this help. GROUP CODE: This is the code of the creditor that will receive remittance advices and cheques when invoices for this creditor are paid. BRANCH: The branch to which this creditor belongs. EXPENSE BRANCH: This is the default branch for dissecting expenses. Invoices and credit notes will default to this branch, while journals and payments are recorded against the creditor's branch. LINK DEBTOR: If the creditor is also a debtor then the matching debtor code can be recorded. This allows the system to display debtor balances on enquires and payments. It is only applicable if the Accounts Receivable Module is installed. -----==<>==----- < Press the Help Key for more help. Press any other key to exit help. > /Help1_2
Powerflex Corporation is pleased to announce the release of PFXbrowse for Windows version 4.0. This PFXbrowse release offers all the features you would normally expect of a Windows application including menus, toolbars, pop ups, bookmarks, find and replace, view file definition, find by any index, form view and the list goes on!
This robust new tool is a must for all PFXplus developers. Contact Powerflex Corporation for further information or to order your copy.
A reminder about the Powerflex Developers Mailing List. To subscribe to the list, just e-mail email@example.com">firstname.lastname@example.org with the command "subscribe pfx-dev-list" in the body of the e-mail. A set of easy instructions on how to use the mailing list will be automatically e-mailed to you when you subscribe.
|Powerflex Developer's Kit 16/32-bit||4.23|
|Powerflex Developer's Kit 32-bit/SQL||4.40|
|Powerflex Runtimes||2.63 to 4.40|
|Powerflex HP 9000, Motorola||4.11|
|PFX C-lib MSDOS—DOS-386||4.20|
|PFX C-lib for Windows||4.40|
|PFXsort for DOS-386, Win32/SQL||4.40|
|PFXbrowse 32-bit with Btrieve||2.00|
|PFXbrowse 32-bit for Windows||4.00|
|Powerflex ODBC Driver||1.01|
|Powerflex Driver for Crystal Reports||1.00|
For further information contact Powerflex Corporation or your local dealer.