9711
9711
9711
Sharing Memory
Creating and Managing Memory-Mapped Files
ON THE COVER
Sharing Memory Gregory Deatz
Sharing memory (and information) among applications was easy in 16bit Windows. Although its a bit more complicated in the 32-bit world of
Win95, Mr Deatz demonstrates how memory-mapped files get the job
done and how to work with them in Delphi 2/3.
30 At Your Fingertips
33 OP Tech
FEATURES
9
Informant Spotlight
15 DBNavigator
37 WebHub
Product Review by Peter Hyde
20 Delphi Reports
42 Rapid Development
DEPARTMENTS
REVIEWS
2 Delphi Tools
5 Newsline
44 File | New by Richard Wagner
Delphi
T O O L S
New Products
and Solutions
Delphi
T O O L S
New Products
and Solutions
Top Support
Ships TopGrid
Top Support has released
TopGrid, a new VCL grid
component for Delphi 2 and
3. TopGrid can replace the
DBGrid and StringGrid
components. It supports
standard Windows behavior,
including normal scrolling
and the ability to use headings as buttons.
The appearance can be
customized by setting fonts,
colors, alignment, and 3D
effects, either on the grid
level or for individual rows,
columns, or cells. Headings
can be multi-line, and the
number of fixed rows or
columns can be set. Also,
rows and columns can be
set to visible or invisible,
and read-only or editable.
The display order of
columns can be set, and
with the unbound version
the display order of rows
can also be changed.
Delphi
T O O L S
New Products
and Solutions
ISBN: 0-672-31024-4
Price: US$59.99
(967 pages, CD-ROM)
Phone: (800) 545-5914 or
(317) 228-4231
Delphi 1, 2, or 3, C++Builder,
and Microsoft Visual Basic
(as well as other 32-bit
environments with OCX
support).
ExceleTel, Inc.
Price: ExceleTel TeleTools, US$199;
ExceleTel TeleTools Express, US$49.
Phone: (919) 233-2232
E-Mail: [email protected]
Web Site: http://www.exceletel.com
News
L
November 1997
Borland Releases
New ObjectInsight
for Delphi 3
Scotts Valley, CA Borland
has shipped ObjectInsight for
Delphi 3, a Delphi sourcecode navigation system that
browses the origin and implementation of object class
properties, methods, and
events. An internal tool developed for the Delphi team,
ObjectInsight enables users to
understand their VCL and
class libraries.
Navigating the Delphi Visual
Component Library (VCL)
source code provides insight
into the techniques used by
the Delphi developers when
building the VCL. ObjectInsight supplements the
Delphi Help system by providing a visual object framework from which to drill into
the Delphi source.
ObjectInsight allows users to
run queries against all information in a project. It features
Nashville, TN Borland
and IBM announced a combined R&D effort to develope Java applications with
JBuilder and IBMs San
Francisco Project.
JBuilder to be installed
world-wide at IBM San
Francisco education centers.
For information, visit
http://www.ibm.com/java/SanFrancisco/.
On the Cover
Delphi / Shared Memory
By Gregory Deatz
Sharing Memory
Creating and Managing Memory-Mapped Files
haring memory under Windows 3.1 was as simple as creating a DLL with
global variables. In Win32, global memory spaces are private to each
process, so its no longer this simple; however, the Windows API provides a simple, safe, and fast mechanism for sharing memory: the memory-mapped file.
Using mutexes (a way of specifying critical code between processes), the developer can create robust applications that can safely read and write shared memory.
This article demonstrates how to create
memory-mapped files and manage them in
critical sections identified using mutexes. It
will also go one step further, showing how
to wrap these API calls in easy-to-use,
reusable objects, including a few surprises
along the way.
The Mechanisms
Win32 introduced the flat address space to
the Windows programming community; with
it came the private address space. You might
have seen Delphis ShareMem unit mentioned
On the Cover
(Mutexes can be used in a single process to manage synchronous
access to global variables, but its much cheaper to use Delphis
TThread.Synchronize method, or to use another suite of native
API system calls: InitializeCriticalSection, EnterCriticalSection,
LeaveCriticalSection, and DeleteCriticalSection. These system
calls manage the application-level CRITICAL_SECTION, and
are well documented in Win32.hlp.)
Now that the MMF has been created and initialized, we can
treat SharedPChar as if its just another pre-allocated character
array. The click event for btnSet merely copies the data in the
Edit control to SharedPChar, and the click event for btnRefresh
copies the data in SharedPChar to the Edit control.
By launching two or more instances of Example1.dpr, you will
see that, indeed, the MMF called MyMemoryMappedFile
shares its data between processes.
Now, we want to clean up after ourselves; the forms OnDestroy
event unmaps and closes the MMF:
UnmapViewOfFile(SharedPChar);
CloseHandle(hFileMap);
On the Cover
The Mutex
Now that weve seen how we can share memory between processes, the next question is, How do we manage this shared memory to ensure orderly access to it? Obviously, we want predictable
results from our MMF, so the prospect that two processes could
be writing to our MMF at the same time is unacceptable.
Mutexes provide mutually exclusive access to critical sections
of code across threads and across processes. Example2.dpr
shows very minor modifications to the original project,
Example1.dpr. We introduce a new handle, hAccessMutex
(which allows us to instantiate a mutex), and two new system
calls, WaitForSingleObject and ReleaseMutex.
We create the mutex using CreateMutex:
hAccessMutex := CreateMutex(nil, False,'MyAccessMutex').
the WaitForSingleObject function will wait as long as necessary (potentially forever, as indicated by the INFINITE constant) for hAccessMutex to be disowned. Windows guarantees that only one process or thread can own a mutex at a
time, so as soon as WaitForSingleObject returns, we are
confident we have exclusive access to the MMF. Because all
other threads that wait on hAccessMutex will wait for this
process to relinquish ownership of the mutex, we must guarantee that ReleaseMutex will be called. Hence, we use the
try..finally construct.
Getting Fancy
At this point, we can stop. We have created an MMF and we
have protected access to the MMF using mutexes. However, it
would be nice if we could wrap this up in an easy-to-use
Delphi object. Our goals for the object are to:
1) share a piece of memory,
2) manage access to the shared memory, and
3) be alerted when the shared memory has been altered by
another thread.
We already have what we need to build an object that does
items one and two, but accomplishing three requires a few
more tricks from the Windows API. In particular, we need to
use threads and events.
8
Conclusion
Sharing memory in Win32 is a bit more complicated than it
was in Windows 3.1, but its still a relatively simple process.
In fact, we have successfully navigated the terrain of quite a
few API tools (MMFs, mutexes, events, threads, and critical
sections), all of which have played an integral role in implementing a robust, easy-to-use object that shares memory
across processes.
The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\97\NOV\DI9711GD.
Informant Spotlight
Delphi / Windows Messages
By Robert Vivrette
Windows Messages
No doubt many of you are familiar with
how Windows talks to itself. Essentially,
every aspect of a Windows programs behavior is the result of a Windows message in
one form or another. For example, when
you enter data into the Notepad application, there are messages flying all over the
place. There are messages that track when
keys go down, when keys go up, when the
mouse moves a pixel, or when its buttons
are clicked, just to name a few.
For the most part, however, the average
Delphi programmer generally only deals
with messages passed around within a single
application; even then you might not really
think they are messages. For example, your
form might have a message handler called
OnMouseDown, but Delphi is really
responding to a WM_MOUSEDOWN
Windows message, and is passing it along
through a hierarchy of message-handling
locations. Ultimately, the message gets to
your form, and control is passed into your
OnMouseDown routine. You could construct a fake WM_MOUSEDOWN message and send it to your form, by using the
SendMessage API call, and your form
wouldnt know the difference. It got a message; thats all its concerned about. Sending
Informant Spotlight
procedure TForm1.LookForApp(StartLooking: Boolean);
begin
if StartLooking then
lblOtherApp.Caption := 'Looking...'
else
lblOtherApp.Caption := 'Test Application #' + OtherApp;
if StartLooking then
TheHWnd := 0;
Timer1.Enabled := StartLooking;
btnStringSend.Enabled := not StartLooking;
btnValueSend.Enabled := not StartLooking;
btnBitmapSend.Enabled := not StartLooking;
OtherAppFound := (TheHWnd <> 0);
Image1.Refresh;
Image2.Refresh;
end;
Informant Spotlight
TForm1 = class(TForm)
...
public
procedure WndProc(var TheMsg: TMessage); override;
end;
procedure TForm1.WndProc(var TheMsg: TMessage);
begin
if TheMsg.Msg = TheMsgVal then
begin
{ It's our custom message -- deal with it! }
end;
inherited WndProc(TheMsg);
end;
We can write a message handler that specifically handles a message with the value MyMsgConst. Because this is a constant, the
compiler knows how to compile it into the final executable.
But in our case, we dont know the value until the program
runs; we obtain it from RegisterWindowMessage.
Enter the WndProc method. As I mentioned before, this is a
sort of clearinghouse for the applications messages. To listen
in on a custom message, all we have to do is override the
applications WndProc method and look for our message value
(which is stored in the variable TheMsgVal). If it is that value,
we handle it accordingly. If it isnt, it gets passed on to the
inherited WndProc handler (the one that was overridden).
Figure 3 shows pseudo-code for this overridden method.
So at this point, we have two running applications that are
aware of each other and have registered a Windows message
to communicate with. We have overridden the WndProc
method, so we know when one of the messages comes along.
Sending a Number
In our first test the easiest one were going to just send
a number to the other application. As mentioned earlier, messages have two fields that can be used to pass custom data. In
this case, lets use the lParam field of the message. As mentioned, lParam is a Longint, so we can pass any number that
would fit in a long integer. Because I designed the test application to be capable of passing other things with this message, well also need to provide something that says this message is holding a numeric value in its lParam field. Therefore,
well use the wParam field to hold one of four constants we
have defined: wpString, wpValue, wpBitmap, or wpShutdown.
(Keep in mind that we could have defined four different messages rather than just one. That would have made this mechanism of categorizing the message by means of the wParam
field unnecessary.)
If you again look at Figure 1, youll see one of our test applications running. To pass a number over to the second application, you simply enter a value in the Value To Send edit box
and click on its associated Send button. When that button is
clicked, the btnValueSendClick method is called; it performs
two simple lines of code. The first is to verify that we have a
Informant Spotlight
procedure TForm1.WndProc(var TheMsg: TMessage);
var
ThePtr : PChar;
TmpBmp : TBitmap;
begin
if TheMsg.Msg = TheMsgVal then
case TheMsg.wParam of
wpString :
if TheMapHnd <> 0 then
begin
ThePtr := MapViewOfFile(
TheMapHnd,FILE_MAP_WRITE,0,0,0);
edtStringRec.Text := ThePtr;
UnmapViewOfFile(ThePtr);
end;
wpValue :
edtValueRec.Text := IntToStr(TheMsg.lParam);
wpBitmap :
begin
TmpBmp := TBitmap.Create;
try
TmpBmp.Handle := TheMsg.lParam;
Image2.Picture.Bitmap.Width := TmpBmp.Width;
Image2.Picture.Bitmap.Height := TmpBmp.Height;
Image2.Picture.Bitmap.Canvas.Draw(0,0,TmpBmp);
finally
TmpBmp.ReleaseHandle;
TmpBmp.Free;
end;
end;
wpShutDown :
LookForApp(True);
end;
inherited WndProc(TheMsg);
Sending a Bitmap
Now lets try something a little more complex: sending a
bitmap from one application to another. The key component
of a bitmap (as far as Windows is concerned) is its handle.
With this handle, Windows can access the rest of the bitmaps
data. Because a windows handle is just a Longint value, we
are going to pass the bitmaps handle across applications in
the same manner as the previous example. However, there is
just a little more to this one.
end;
Informant Spotlight
function TForm1.ObtainMappingHandle: THandle;
begin
Result := CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,
0,2000,TheMappingConstant);
if Result <> 0 then
// Did it return a valid handle?
if GetLastError = ERROR_ALREADY_EXISTS then
// Did it already exist?
begin
CloseHandle(Result);
// Close this and we will open an existing.
Result := OpenFileMapping(FILE_MAP_WRITE,False,
TheMappingConstant);
end;
end;
procedure TForm1.btnStringSendClick(Sender: TObject);
var
ThePtr : PChar;
begin
if (TheHWnd = 0) or
(TheMapHnd = 0) then
Exit;
ThePtr := MapViewOfFile(TheMapHnd,FILE_MAP_WRITE,0,0,0);
StrPCopy(ThePtr,edtStringSend.Text);
SendMessage(TheHWnd,TheMsgVal,wpString,0);
UnmapViewOfFile(ThePtr);
end;
Memory-Mapped Files
Our last example is the use of memory-mapped files
(MMF), which is simply a way of sending an arbitrary
block of data between two cooperating applications. This is
done by obtaining a handle to a common piece of memory
space from the operating system. When both applications
have a handle to this space, they can write information
back and forth to each other. The concept of an MMF is
similar to creating a disk file and writing out the data while
letting the other application open it up and read it.
However, MMFs have a number of advantages over this
kind of mechanism. As I mentioned earlier, Gregory
Deatzs article covers this topic in much greater detail, so
Im just going to hit the high points.
To illustrate this feature, were going to send a string
between the test applications. This string will be a Delphi
2/3 HugeString rather than the 256-character Delphi 1
string. The first step we must take when preparing to move
data with an MMF is to create the file mapping. This will
be a handle that Windows will use to access our mapped
file space. I did this in the FormCreate method using a call
to a small function I created called ObtainMappingHandle
(see Figure 6).
The two Win32 API calls I use in this function are
CreateFileMapping and OpenFileMapping. CreateFileMapping
creates a new mapping, and OpenFileMapping opens one that
already exists in the system. What Ive done here is to try to cre13
Informant Spotlight
accomplish these same tasks, but passing messages and
using memory-mapped files are two of the easier ways your
applications can talk.
The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\97\NOV\DI9711RV.
Robert Vivrette is a contract programmer for Pacific Gas & Electric, and Technical
Editor for Delphi Informant. He has worked as a game designer and computer
consultant, and has experience in a number of programming languages. He can
be reached via e-mail at [email protected].
DBNavigator
Delphi 3
An Overview
There is only one more Decision Cube component: the DecisionPivot, which is analogous
to a DBNavigator, in that it permits the user
to more easily manipulate a non-visual control
(the DecisionCube, in this case).
There are a number of steps to consider when
using the Decision Cube components:
Define a DataSet that includes at least one
dimension field and one summary field.
Configure a DecisionCube using the data
from the DataSet.
Point a DecisionSource to the
DecisionCube.
DBNavigator
Figure 3: The Decision Query Editor showing only the query fields.
DBNavigator
DBNavigator
Using a DecisionPivot
The DecisionPivot is a visual control that provides a run-time
interface to certain essential properties of a DecisionCube. Its
purpose is to permit users to select dimensions to expand or
collapse, and the summary values to display.
To demonstrate the use of a DecisionPivot, place one on the
form. Align it to alTop, and set its DecisionSource property to
DecisionSource1. To finalize the decision form at this point,
you may also want to align the DecisionGrid to alTop. Next,
add a Splitter component, align it to alTop and set its Cursor
property to crVSplit. Finally, align the DecisionGraph to
alClient. An example of these components in this orientation
can be found in the project named DECCUBE (see Figure 8).
To change the summary value displayed, select the left-most
button on the DecisionPivot and choose the summary value
to display. To collapse or expand a dimension, click on the
button associated with the dimension. To change a dimension from a row to a column, or a column to a row, drag the
button associated with the dimension from its current position to a new position. Buttons that appear to the right of
the small table icon with horizontal stripes in the
DecisionPivot define the row dimensions. The buttons
appearing to the right of the small table icon with vertical
stripes define the column dimensions. To change the order
of dimensions on a particular axis, drag the buttons associated with the dimensions in question to new positions. The
left-most button represents the highest-level dimension on
its axis, the second left-most button represents the second
dimension along that axis, and so forth.
You can use two or more DecisionPivot components for a
single DecisionSource. This is usually done to place only row
DBNavigator
dimensions on one DecisionPivot, and only column dimensions on another DecisionPivot. You control which types of
dimensions appear on a given DecisionPivot by using its
Groups property. You can also define a DecisionPivot to only
display the drop-down list for available summaries.
When using more than one DecisionPivot, align them differently. For example, align your column-dimension DecisionPivot
to alTop, and the row DecisionPivot to alLeft. You can change
the GroupLayout property of this control to permit the buttons
to appear vertically, rather than horizontally. However, remember that changes to the dimensions of a DecisionCube can take
a long time, depending on the number of discrete levels of the
dimensions involved. Using the methods of the DecisionSource
component, such as OnBeforePivot and OnNewDimensions, you
can ask the user to confirm the changes.
For more information on using DecisionCube components,
see Chapter 16, Using Decision Support Components, in
the Delphi 3 Developers Guide that ships with Delphi.
Conclusion
The Decision Cube components constitute a powerful collection for manipulating and displaying data for decision-support
applications. Its one more reason to consider investing in a
copy of Delphi 3 Client/Server edition.
19
Delphi Reports
Delphi 1 / Delphi 2 / Delphi 3
By Warren Rachele
user clicks the Print button of the shiny new version of SUPER APP, expecting a near-instant reaction from the printer. Instead, he is greeted by the
sound of a grinding disk drive struggling to load the report engine. This test of
patience is amplified by the brevity of the two-line report that eventually
appears in the printer tray.
We developers must recognize that this kind
of small annoyance leads to a greater overall
dissatisfaction with our products. Fortunately,
this situation is easily preventable by adding
low-level understanding of the WIN32 API
and Delphi print functions to your quiver of
techniques.
The developers task is to measure the quantity and style of the required output against
the effort and overhead of programmatic
solutions. Simple text and formatted database
reports are sometimes better handled by
manually manipulating the Windows printer
functions, rather than loading a print engine
such as ReportSmith, with all its associated
overhead. As with most development tasks,
Delphi provides a number of approaches.
A Printing Primer
To lay a foundation for Delphi techniques, a
quick primer on Windows printing is in
Function
Role
AbortDoc
EndDoc
EndPage
SetAbortProc
StartDoc
StartPage
Delphi Reports
Property
Role
Aborted
Canvas
Capabilities
(32-bit only)
Copies
(32-bit only)
Fonts
Handle
Orientation
PageHeight
PageNumber
PageWidth
PrinterIndex
Printers
Printing
Title
Centering Text
uses Printers;
proc PrintSomething;
begin
Printer.BeginDoc;
Printer.Canvas.Textout(10,10,'This is some text');
Printer.EndDoc;
end;
TPrinter Properties
The key property of the TPrinter object is Canvas, which is
used the same way for the printer as for a form. Text and graphical output is directed to the printers canvas, from which it is
converted to printer commands and sent to the device. To use
the canvas and begin a print job, the developer calls the printers
BeginDoc method. This will open the canvas surface to receive
output. All Canvas methods are then available to produce the
desired format. When a page of output has been laid out on the
canvas, the printers EndDoc method is used to send the output
to the printer. The Abort method can be called to discard a
print job. The NewPage method, alternately, will send output to
the printer, then start working on a new canvas page.
The methods used to format the output on the canvas differ in
their abilities. Ill present them in order of declining precision,
with regard to placing text on the canvas.
Canvas Differences
Before continuing, lets note some important differences in
canvas usage. Coordinates for printed output lie along a
plane that is much more dynamic than that of a videooutput device. Changing the resolution for the applications
carefully designed output can happen as quickly as the user
can switch from the network laser printer to the local line
printer or fax software. X and Y on the laser-printer plane
will not fall in the same spot on lower-resolution print
devices such as the line printer. Hard-coded coordinates will
21
The power of the TextOut method lies in its ability to precisely place the output at a desired location on the document, through the X and Y coordinates. Consider the
placement of a centered report title. To supply the proper
starting location to the TextOut method, the developer
must do some simple calculating within or before
the call. Figure 4 shows a snippet for centering text on the
applicable printer.
To determine the center of the canvas as defined by the printer interface, the PageWidth property is queried. This property
contains the width of the page measured in pixels. This value
is divided in half to determine the approximate center line of
the page. A pair of subcalculations provides the center of the
text string. The TextWidth method of the canvas provides the
width, in pixels, of the string data passed to it.
In the example, the function is passed a single character, and
returns a base-width value per character to use as a basis for
computing the length of a full line of text. The base value is
then multiplied by half the number of characters in the text line
to be printed. This equation returns a factor in pixels which,
when subtracted from the center point of the canvas, gives a
starting point to the X parameter of the TextOut method.
Delphi Reports
{ Get vertical pixels per inch }
VPixelsPerInch := GetDeviceCaps(Printer.Handle,LOGPIXELSY);
{ Set line spacing 1/10 of vertical pixels per inch }
VLineSpacing := VPixelsPerInch div 10;
{ Set the Line Height }
LineHeight := Printer.Canvas.TextHeight('A') +
VLineSpacing;
var
OutFile : TextFile;
begin
AssignPrn(OutFile);
Rewrite(OutFile);
Writeln(OutFile,'This is a print line');
still more work on the part of the developer. To print multiple lines along the Y axis of the page, the developer must
compute the line height of the applicable font, add some
spacing between the printed lines, and maintain a sum of
printed lines so the end of the page is not overwritten and
output is not lost. The first step in this process is to retrieve
a measure of the resolution in pixels per inch, using the following API call:
GetDeviceCaps(Printer.Handle,LOGPIXELSY);
CloseFile(OutFile);
end;
Printer Peculiarities
While most methods from the Form object canvas will work on
the Printer canvas, there are some important differences. The
printer is a mechanical entity with a unique set of exceptions
that must be handled by the developer. The items most likely to
trip up the programmer are mechanical errors generated by the
printer. The program calling the Printer object directly will be
responsible for handling out of paper or printer not online
errors gracefully. Calls to the Printer methods should be
enclosed within a resource-protection (try..finally) block, so that
all resources are released, and all exceptions are handled.
Exception handling aside, the output capabilities of the two
devices must be considered by the developer. Because the user
will be running Windows, the developer is guaranteed that
graphics can be displayed on the screen. There is no guarantee
however, that the printer selected is capable of reproducing
them. Text and graphics sent to the screen can be erased; output to the printer canvas is sent to the printer, and cannot be
recalled. Therefore, the user should be given the option of
printing graphics or leaving them out, based on the capabilities of the chosen printer.
Finally, drawing to the screen canvas is instantaneous, while
drawing to the printer canvas is not. Sending output to the
printer is slow, so the developer should always provide the
ability to abort a print process. For this reason, the TPrinter
object provides the Abort method.
Delphi Reports
must be called to create a new output file. Once the output
file has been created, the Write and Writeln procedures are
used to send the output text to the file name.
Our discussion ends with the least-powerful technique. By
calling the Print method of a Form object, the entire form
canvas is sent to the currently selected Windows printer.
The layout is then in control of the forms canvas; the
amount of data that can be output is constrained by the
size of the form.
Conclusion
Each of the techniques presented has trade-offs that must be
weighed by the developer when considering the best way to
produce printed output for the user. The most basic consideration regarding any of these is the amount of programming needed to provide the user with the fastest method of
receiving output.
NextCol := NextCol +
(Table1.FieldByName('COMPANY').DisplayWidth *
Printer.Canvas.TextWidth('A'));
Printer.Canvas.Textout( NextCol,LinesPrinted,
Table1.FieldByName('COUNTRY').AsString);
23
Table1.Next;
LinesPrinted := LinesPrinted + LineHeight;
end
else
begin
Printer.NewPage;
LinesPrinted := 0;
PrintTimeStamp;
PrintHeading;
end;
end;
Printer.EndDoc;
end;
By Bill Todd
Table Pathfinding
If a table uses a permanent BDE alias, you can obtain its
path easily using the GetAliasParams method of TSession (see
Figure 5). However, GetAliasParams will not work if the
alias is a temporary one created by a TDatabase, or if its
hard-coded in the DatabaseName property. The function in
Figure 6 uses BDE API calls, and works in both these situations, as well as with a table whose BDE alias is permanent.
The call to AnsiToNative translates the tables TableName
property to the local BDE character set, and converts it to a
null-terminated string. DbiGetCursorProps fills the
CURProps structure with a variety of properties about the
cursor. For a complete list of the properties returned, see the
DbiGetCursorProps function in the BDE API online Help
Copying Tables
Although you can copy local tables using the Delphi
BatchMove component, this technique has a major disadvantage in that only the data is copied. Indices are not copied,
and in the case of Paradox tables, neither is the .VAL file that
contains the validity checks and referential-integrity definitions. The dgCopyTable procedure in Figure 7 will copy a local
table, including its family (indices and .VAL file), to the
same or a different directory. This procedure will also copy
server tables, but only within a single database.
This procedure begins by converting the source and destination table names to null-terminated strings, and also to
the BDEs character set, by calling AnsiToNative. Next,
DbiCopyTable is called to actually copy the table. In this
call, the tables type parameter is set to nil; therefore, the
names of local tables must include file extensions.
Network Usernames
If you need the users network username, use the BDE API
function DbiGetNetUserName, as shown in Figure 8.
Regenerating Indices
Regenerating a non-maintained index brings it up to date, so it
includes all the data in the table. Regenerating both maintained
and non-maintained indices compacts and balances them for
best performance. You regenerate all of the indices associated
with a Paradox or dBASE table calling DbiRegenIndexes:
DbiRegenIndexes(Table.Handle)
Next, you need a procedure to load the DLL and get the
function addresses via the Windows API LoadLibrary and
GetProcAddress functions (see Figure 9). This procedure
begins by getting a handle to the DLL, then gets the
addresses of the functions by calling GetProcAddress. You
should call this function once when your main form, or the
form that uses these functions, is created.
When the form is destroyed, call:
FreeLibrary(Idapi);
follow are included in the main forms unit, and are used
by the code that creates the alias:
var
Database:
rslt:
TmpCursor:
i:
AliasFound:
DBDesc;
DbiResult;
hDbiCur;
Integer;
Boolean;
The fourth parameter is a string containing all the parameters required for the driver type specified in the third
parameter. For the standard driver, the only parameter is
the path to the database. In this example, the path is set to
the directory that contains the programs .EXE file. If you
plan to add an alias to a database server, see the examples
in the BDE online Help file for the structure of the parameters string.
28
More to Come
Theres much left to cover, but we can absorb only so
much at one sitting. Next months installment will explore
Bill Todd is President of The Database Group, Inc., a Phoenix-area consulting and
development company. He is co-author of Delphi: A Developers Guide [M&T
Books, 1995], Creating Paradox for Windows Applications [New Riders
Publishing, 1994], and Paradox for Windows Power Programming [QUE,
1995]; a contributing editor of Delphi Informant; a member of Team Borland;
and a speaker at every Borland Developers Conference. He can be reached at
(602) 802-0178, or on CompuServe at 71333,2146.
29
At Your Fingertips
By Robert Vivrette
hen Delphi 2 was released, Borland had a little secret. Those who knew
were threatened 50 lashes with a wet noodle if they told. The secret was
an Easter egg in the form of a window that showed disassembled code (i.e. the
assembly-language code generated by the Delphi compiler). Borland didnt
make the Disassembly View window common knowledge, because it was a bit
experimental and had a few small visual bugs.
Delphi 3 contained an even fancier multipane version, but Borland was still maintaining secrecy. Rats! The pressure had been
killing me for over a year now; that is, until
I saw a reference on Borlands Web site
about how to turn on this viewer. With the
cat apparently out of the bag and the wetnoodle threat removed, I bring you the
Disassembly View! Simply open RegEdit
and go to the following key:
HKEY_CURRENT_USER\Software\Borland\Delphi\
3.0\Debugging
At Your Fingertips
procedure TForm1.WMEndSession(var Message: TWMEndSession);
var
Reg : TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey('\Software\Microsoft\Windows\' +
'CurrentVersion\RunOnce',True) then
Reg.WriteString('MyApp','"' + ParamStr(0) + '"');
finally
Reg.CloseKey;
Reg.Free;
inherited;
end;
end;
31
At Your Fingertips
Specify the top/left position of a form when its maximized by setting the ptMaxPosition field.
Specify the maximum width and height of a form when
its maximized by setting the ptMaxSize field.
To intercept this message, put a message handler in the private
section of your form declaration, like this:
private
procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
Robert Vivrette is a contract programmer for Pacific Gas & Electric, and Technical
Editor for Delphi Informant. He has worked as a game designer and computer
consultant, and has experience in a number of programming languages. He can
be reached via e-mail at [email protected].
32
Op Tech
Delphi 3 / COM
By Ron Mashrouteh
Multi-Tier Development
Building a Remote Server Application with Delphi 3
One of the key features in developing multitier applications is a remote data module
(RDM); youll create one in this article.
RDM is new to Delphi 3 Client/Server Suite,
and is an integral part of remote server applications. The RDM is a repository that maintains all database components and process
requests from client machines.
Op Tech
Op Tech
SQL statements in the Query1.SQL property. We need
to provide an interface so our client can send a dynamic
SQL statement to Query1, and let it process the query on
the server.
In the MyServer RDM, select the Provider1 component,
right-click to display the properties menu, and select the
first menu choice, Export Provider1 from data module (see
Figure 6). Note that the second member is added to the
Type Library window on the Members page. Again, press
Refresh from the toolbar to synchronize the unit with this
change. Now we have to write a few lines of code for the
server application to process the request from the client
application. We need to add the following code to the
OnDataRequest event of Provider1:
Save and run the program; thats it for our Server application.
with DataModule1.ClientDataSet1 do
Data := Provider.DataRequest(StrSql);
end;
The remote database application server could and probably would be sitting on another machine, in which case
youd need to assign a value to the RemoteServer1s
ComputerName property. Once you launch the server application on a different machine, it automatically registers itself
with the Windows registry.
Project | Build
All
Op Tech
Running the Application
When the client application is up and running, enter:
SELECT * FROM ANIMALS
Conclusion
Two advantages of developing n-tier applications are that
theyre easier to maintain and deploy. Such applications
can become complex depending upon the number and
type of triggers, stored procedures, business rules, etc;
however, once the server application is configured, no individual BDE set-up or configuration is required on the
client machines.
The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\97\NOV\DI9711RM.
36
WebHub
HREF Tools Extensible, OOP-Based Framework
for Web Site Creation
Down to Cases
Description
TWebApp
Represents and manages the Web application as a whole. It provides direct pointers to the essential
components and properties needed while writing a Web application. Variants of this component
are provided that have cut down or extended HTML macro processing, including easy
database-access macros.
Does for surfers what TWebApp does for the application as a whole. It tracks each surfers data
from arrival at the site, including data entered on HTML forms, to application-specific surfer
data and component-state data.
Encapsulates the properties of the Web server being used. It presents all the CGI and system
environment data passed from the Web server to the application as accessed string and list properties.
Sends output from the application to the Web server. Typically, the output consists of HTML,
but it can be a different MIME type (e.g. binary). Furthermore, this component can perform
WebHub macro expansion during transmission, allowing a programmer to send HTML chunks
and macros that have been defined externally by the HTML specialist.
Connects the application to the Runner and the Hub, and brings each page request into the
application. Best regarded as the input part of the equation, while TWebAppOutput is the output.
Keeps track of other components it monitors open applications, open pages and sessions, and
assists in saving state. It is the closest thing to a control component in WebHub.
Automatically checks other WebHub components and builds a menu of their management functions,
providing an instant user interface.
A component class that can be directly called from HTML to perform a given task, enabling
access to pieces of reusable custom Delphi logic.
Presents tabular data such as static database tables or the results of dynamic queries. It automatically
manages display of different data types, with surfer-selectable index order, display sets, and page
sizes (if desired). It provides paging controls, but these can be overridden by the developer. Hot
links can be made within any field these links can be jumps to detail pages or links to
resources such as audio or image files. Other property changes can turn a TWebDataGrid table
into a grid of editable fields, allowing for database display and update.
Provides the core functionality of TWebDataGrid, but enables the developer to output each row
of the data, providing a flexible free-form output and table format. (Related components include
TWebDropDown, TWebOutline, TWebStringGrid, and TWebListGrid.)
Handles paged display of non-database lists and information, useful for result sets from a source
other than a database query (i.e. an indexing engine).
Supports instant form presentation and provides display-only and editable views of database
fields. A simple macro parameter determines which view will appear, and a TWebDataPoster
component can be added to handle table updating.
Used to verify credit-card number and expiration by applying standard number-validation techniques.
Takes surfer form input and automatically e-mails data to a preset destination. Fully configurable,
allowing one Web application to support many sites, forms, e-mail templates, and target addresses.
For developers creating mail applications, such as bulk e-mailing from a site, the TWebMail
component provides lower-level functionality.
Makes Web-page requests from within a Web application useful for sites that need to watch
or draw information from other sites. Like TWebMail and TWebTelNet, this component depends
on the TWebSock component that can also be used to implement direct TCP/IP communications
with external services, or even with applications on the surfers machine, such as a Java applet.
Creates on-the-fly, custom images for display in Web pages. Similar logic can be used to convert
images from database BLOBs into files that can be viewed in a browser.
TWebSession
TWebServer
TWebAppOutput
TWebCommandLine
TWebInfo
TWebMenu
TWebAction
TWebDataGrid
TWebDataScan
TWebScanList
TWebDataForm
TWebCreditCard
TWebMailForm
TWebHTTP
TWebPicture
39
WebHubs Architecture
A quick look at WebHubs architecture will clarify how this is
possible (see Figure 3). The blocks shaded in blue represent features common to all automated Web sites. However, they also
hide many areas of complexity, such as the extent to which the
Web application needs to be customized to match different
server interfaces, or extended to handle some of the queuing
and resource features noted earlier.
The green blocks are unique to WebHub. The Runner is a
small executable or DLL (typically 100KB) that manages all
server-specific logic. Select a different Runner from the set
supplied with WebHub, and you can support a different
server-interface protocol (e.g. ISAPI, CGI-WIN, or CGIBIN). In the case of the CGI protocols, the fact that the
Runner is small is a major bonus. As CGI loads and unloads
the target application for each request, enormous overhead is
avoided because Runner is tiny.
The Hub provides bulletproof request queuing and also acts
as a manager for Web applications deployed on the site.
With the Runner, the Hub implements the basic surfer identification and tracking mechanisms that make saving-state
and several other advanced features possible. Significantly,
the Web application in this model now becomes a lite version. It no longer needs to be concerned about server, queuing, or resource issues, and instead can use the custom pagegeneration and back-end system interaction logic on a one
request for one surfer at a time basis.
The full potential of this architecture is seen early on in the
development process. For example, instead of trying to debug
complex, thread-safe DLLs that can crash the entire Web
server when an error occurs, WebHub developers can do
their development and live debugging in the Delphi IDE.
Application bugs dont affect the Hub, Runner, or Web server,
and new versions can be built, deployed, and tested in seconds without taking the server down.
40
Critically Speaking
If there is any bad news about WebHub, it must center on the
fact that its a sizeable product that is being continually revised
and extended. The learning curve for the basics is quite good,
but with over 30 Web components and 70 other utility components available, it would take a very confident developer to
claim in-depth knowledge of the entire product. This is not
helped by the fact that (at the time of this writing) many properties for non-core components are not fully documented in the
3MB Help file and 200-page manual that ships with WebHub.
Furthermore, with new revisions and features being released
at a steady pace, a sensible eye must be kept on version management, especially when deciding which WebHub version to
use for deploying a completed site. On the other hand, the
free e-mail list server provides excellent, friendly, and timely
technical support. The range of example applications provided with WebHub covers most common and many
uncommon Web site needs. These include surfer authentication, a shopping cart, form mail, integration with
VRML, Java and JavaScript, remote administration, live
graphic creation, data-driven queries, database search/detail
drill down, database editing, surfer-driven site customization,
multilingual pages, and more.
Peter Hyde is the author of TCompress and TCompLHA component sets for
Delphi/C++Builder, and Development Director of South Pacific Information
Services Ltd., which specializes in dynamic Web site development and is the New
Zealand technical support center for WebHub. He can be reached by e-mail at
[email protected] or via http://www.spis.co.nz.
41
TextFile
TextFile
43
File | New
Directions / Commentary
Beyond Hype
Two Technologies with Staying Power
n todays climate, determining whether a new software technology is a long-term trend or a quick-hit fad
can be a full-time job. Indeed, thumbing through the pages of PC Week, InfoWorld, etc., one can easily
succumb to the seemingly endless dribble of hype that permeates our industry. If were not on guard, we
might actually begin to believe it.
Borland: Please avoid using
the horribly overused words
Builder and Studio in
such a products name.)
Dynamic HTML
Studios
For most developers, the days
of working with a single
development tool are over.
With the diversity of application architectures desktop,
client/server, three-tier,
intranets, extranets, and the
Internet you may have to
employ several tools for a single application. Its safe to say
that, in addition to working
primarily with Delphi, many
of you work with other development
tools along the way.
Until recently, little integration existed
across development-tool environments
even from the same vendor. This is
changing, and in some important ways.
Microsofts Visual Studio was the first
integrated development environment
that allowed you to work with multiple
development tools from within the
same IDE shell. So, whether you use
Microsoft Visual InterDev, Visual C++,
or Visual J++, you can work within the
44