June/July 1999: Volume 9 Number 2

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

PFXplus Version 4.40 is Here!

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.

New Features Galore!

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

PFLN

32-bit Windows graphical runtime, runs character-mode and graphical-mode programs under Windows 95/98 and NT.

PFLT

32-bit Windows Console Mode runtime, runs character-mode programs in a Console under Windows 95/98 and NT.

PFL3

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.

Top of Page

PFX C-lib as a TDataSet in Delphi

Alexander Halser – EC Software halser@ec-software.com

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

The TDataSet Mystery

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.

TDataSet Relationships

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).

Powerflex buffers and field objects

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;

GETFIELDDATA Copies Record Buffer to TFIELD Object Buffer

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/.

Top of Page

Sizing Your Screens

Christine Charalambous – Powerflex Corporation – Australia

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.

Screen Size with PFLN

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.

Fonts in C-Mode Applications

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:

sConsoleFont=Terminal,7,12

sets font width to 7, height to 12. For a 800x600 desktop resolution sConsoleFont may be set to

sConsoleFont=Terminal,9,16

The following examples demonstrate the difference in screen size.

sConsoleFont set to Andale Mono

sConsoleFont is set to Andale Mono

sConsoleFont not set

sConsoleFont is not set (default)

Monospace fonts

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/.

Screen Resolution

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.

Screen Size with PFLT

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.

Maximising Screens

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.

Top of Page

Utility Program for Y2K Data

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 pfxsales@pfxcorp.com. 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!

Top of Page

Technical Tip No. 41

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

Top of Page

Converting Character-based Help to Windows .HLP Files

Jon Wilson – Nexus Business Software

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.

Implementation

  1. Use a Help Authoring Tool
    We obtained a help authoring tool (a demo version of ForeHelp), created some simple help projects, and studied the resulting RTF file. With the aid of the RTF help file (also available free from Microsoft - actually it’s part of the help compiler 'kit') we became familiar with the RTF formatting instructions in general and with the particular ones we needed to produce a fairly simple but adequate Windows help file. Given the length of this article it is really beyond its scope to describe the RTF formatting commands. Eventually we only had to use a handful to achieve our aims. The biggest challenge was picking font sizes, setting tabs and left margins. For instance we learnt that a 'twip' is 1/1440 of an inch and is used as the unit of measure in RTF help documents!
  2. Standardise help screen structure
    We realised that to get any sort of consistency in our Windows help we had to structure our character based help in a very formal and regular manner.

    Armed with our basic understanding of RTF and the limitations of character based help we devised a set of formatting rules for our character help screens. Some of these rules were as follows:
    1. Lines starting in column position 1 and preceded by a blank line are considered to be new paragraphs. If not preceded by a blank line they are considered to be continuations of an existing paragraph.
    2. Lines starting in column 5 were indented paragraphs.
    3. Lines starting with a dash in column 5 were indented to the dash then indented again for the text following the dash... etc.

    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
  3. Write the conversion program.
    Strange to say, this turned out to be relatively straightforward once we understood the requirements and had converted our character-help. The conversion program was itself written in Powerflex and took as input a list of files (e.g. all the programs for the Accounts Receivable module). It then 'parsed' the files, and stripped out and formatted all the help text according to RTF specs, eventually outputting a .RTF document that was fed into the help compiler. This was amazingly fast – it created help for about 1000 programs in under 15 minutes, including running the help compiler.

    Along the way we were able to do some little extras like creating a sample version of all our reports so they could be viewed from the Windows help without running the report. We are very happy with the result as we are now able to keep one version of help (the character version) but still have our Windows product produced with very acceptable Windows help. In fact in our GUI version we give users access to our GUI help by default, but they can also access our character help (as this may be more up to date).

    The format of your character help screens may be different from ours, but the principles are the same. For this reason, unfortunately, it is impossible to provide a general purpose conversion program.

    However, samples of original help screens, and the corresponding Rich Text Format (RTF) and Help (HLP) files have been made available as a ZIP file here on the Powerflex Web site.

    DOWNLOAD THE SAMPLES NOW

Top of Page

Powerflex News


PFXbrowse for Windows

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.

Mailing List Reminder

A reminder about the Powerflex Developers Mailing List. To subscribe to the list, just e-mail listserv@pfxcorp.com">listserv@pfxcorp.com 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.

Current Versions of PFXplus Release Software

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 SCO/LINUX 4.30
Powerflex RS/6000 4.11
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.

Top of Page