Professional Documents
Culture Documents
ON THE COVER
5 OP Tech 32 Sound+Vision
Two-tier Database Development — Bill Todd DirectDraw Delphi — Brian Phillips
Bill Todd shows you how to build and deploy a two-tier client/server Microsoft’s DirectDraw is a proven technology, but requires some
application using dbExpress, one of the major new features in Delphi know-how to make it perform as advertised. Brian Phillips shares
6, and the sample InterBase database that ships with Delphi. several techniques and details a sample implementation.
FEATURES
REVIEWS
10 On Language 37 ExpressBars Suite 4
Console Applications: Part II — Mike Edenfield Product Review by Ron Loewy
In Part II of his console programming series, Mike Edenfield shares
several advanced features that console applications offer program- 40 SQL Tester 1.2
mers, including screen buffers, view windows, and console modes. Product Review by Bill Todd
43 Building Delphi 6 Applications
16 In Development Book Review by Tom Lisjac
Custom Key Bindings — Cary Jensen, Ph.D.
Cary Jensen introduces custom key bindings, a little-known feature of 44 Building B2B Applications with XML
the Delphi Open Tools API that permits you to add custom keystroke Book Review by David Ringstrom
combinations to the Code editor. And it works for Kylix too! 44 Special Edition Using XHTML
Book Review by Robert Leahey
21 Columns & Rows
45 UML Explained
Using the XML Features of SQL Server 2000: Part II
Book Review by Alan C. Moore, Ph.D.
— Alex Fedorov
This month, Alex Fedorov describes how Delphi interacts with the 45 SQL in a Nutshell
Microsoft XML Parser, before moving on to the main topic of how to Book Review by Christopher R. Shaw
query SQL Server 2000 for XML data from Delphi.
DEPARTMENTS
25 Kylix Tech 2 Delphi Tools
Kylix Program Launcher — Brian Burton 46 Best Practices by Clay Shannon
Brian Burton demonstrates a pure Kylix alternative to using shell 48 File | New by Alan C. Moore, Ph.D.
scripts to launch a Linux application that requires shared libraries,
without requiring technical finesse from the user.
28 Greater Delphi
Introduction to COM — Alessandro Federici
Alessandro Federici explains the fundamentals of COM and the rea-
sons it’s important — from its underlying principles to a concrete
example you can download and examine for yourself.
1 September 2001 Delphi Informant Magazine
ModelMaker Tools Releases ModelMaker 6.0 for Delphi
Delphi ModelMaker Tools announced
T O O L S the release of ModelMaker 6.0, a
two-way productivity and UML-
New Products style CASE modeling tool.
and Solutions ModelMaker 6.0 generates and
reverse-engineers native Delphi
source code.
ModelMaker 6.0 features
extensions in the code model,
such as method overload support
and custom property access spec-
ifiers, which give near-100 per-
cent compatibility with Delphi
source code. A new code
importer eliminates virtually all
import restrictions. Code gener-
ation allows more customization
such as assigning or maintaining values. Enhancement of the ing capabilities.
a custom code order and highly ModelMaker Tools API enables Check the ModelMaker Web site
simplified generation and import creating plug-ins for reporting, for a free fully functional demon-
of “in-source” documentation. inserting code fragments, etc. stration of ModelMaker 6.0.
The Code Template palette sim- Existing interfaces have been
plifies creating and applying enriched and new ones, such ModelMaker Tools
snippets of related code. The as access to the Unit Code Price: Single-user license, US$269;
Creational wizard automatically Explorer, have been added. 10-user license, US$995; site license,
inserts and maintains code to Improvements in the GUI and US$1,995.
initialize and destroy class fields other functions improve over- Contact: info@modelmakertools.com
and assign property default view, navigation, and restructur- Web Site: http://www.modelmakertools.com
Eldean AB
Price: Trial version, free; complete ver-
sion, US$49.
Contact: essmodel@eldean.se
Web Site: http://www.essmodel.com
By Bill Todd
d bExpress is one of the major new features in Delphi 6. This article takes a look
at how to build and deploy a two-tier client/server application using dbExpress
and the sample InterBase database that ships with Delphi. [For an introduction to
dbExpress, its place vis-à-vis Delphi 6 and Kylix, deployment, and similar issues, see
Bill Todd’s article “dbExpress” in the March 2001 Delphi Informant Magazine.]
If you have built applications using the MIDAS form. The main form contains a page control
components in a prior version of Delphi, you’ll with three tabs labeled Employee Sales, Custom-
find yourself on familiar ground when building ers, and Schema. The Employee Sales tab shows
your first dbExpress application. If you haven’t the Employees table and the Sales table from the
used MIDAS, your first dbExpress application sample InterBase database in a master-detail rela-
will involve a lot of new concepts. The best way tionship. The combo box at the top of the form
to get through the basics is to walk through build- lets you select a department. The application then
ing the sample application that accompanies this displays the employees in the selected department
article (see end of article for download details). and their sales records. The Customers tab lets you
view and edit the data in the customer table. The
Creating the Application Schema tab lets you view information about the
The first step in building a typical two-tiered database schema.
client/server application using dbExpress is to
create a new project and add a data module. Figure To build this application, start by dropping a
1 shows the finished data module for the sample SQLConnection component on the data module.
application. Figure 2 shows the application’s main Then type EmpConn as the name for the compo-
nent. Next, select the DriverName property in the
Object Inspector and choose InterBase from the
drop-down list. Once you have selected a driver,
you can go to the Params property and open the
property editor (shown in Figure 3), where you
can set all the properties of the driver.
procedure TEmpDm.LoadIniSettings;
var
Ini: TIniFile;
begin
Ini := TIniFile.Create(ExtractFilePath(
Application.ExeName) + 'Emp.ini');
try
EmpConn.Params.Values['Database'] :=
Ini.ReadString('Database', 'Database', '');
finally
Ini.Free;
end;
end;
property to the SQL statement you want to use. In this case the
Figure 2: The example project’s main form. SQL statement is:
Although you don’t need to change it, take note of the CommandType
property. This property controls what you can enter in CommandText.
When set to its default value of ctQuery, CommandText holds
the SQL statement you want to execute. When CommandType is
ctTable, CommandText provides a drop-down list of table names;
when CommandType is ctStoredProc, CommandText contains a
drop-down list of stored procedure names. Add a DataSource
component and connect it to the EmpDs SQLDataSet and name
it EmpLinkSrc. Add a second SQLDataSet component and set its
name to SalesDs. Then set its SQLConnection property and its
CommandText property to:
in this article loads the database path from the EMP.INI file by Set the SalesDs DataSource property to the EmpLinkSrc Data-
calling the LoadIniSettings method, shown in Figure 4, from the data Source component. This will cause the SalesDs SQLDataSet to get
module’s OnCreate event handler. the value of its :EMP_NO parameter from the current record in
EmpDs, and also cause the Sales records to be embedded in the
Go to the DataSnap page of the Component palette, and add a Employee dataset as a nested dataset field. Next, add a DataSetProvider
LocalConnection component to the data module. This is a new from the Data Access page of the Component palette, and set its
component added in Delphi 6 for building two-tier applications DataSet property to the Employee SQLDataSet, EmpDs.
where the DataSetProvider and the ClientDataSet are in the same
application. While putting a ClientDataSet and its provider in the The final two components you need to supply data to the
same application is nothing new, in prior versions of Delphi there Employee Sales tab on the main form are a ClientDataSet and a
were limitations caused by lack of a connection component. These DataSource. Set the DataSource’s DataSet property to connect it
include a lack of access to the connection component’s methods, to the ClientDataSet. Set the RemoteConnection property of the
events, and properties, particularly the AppServer property. The ClientDataSet to the LocalConnection component, then set its
LocalConnection component is optional, but including it gives ProviderName property to the DataSetProvider for the Employee
you access to its properties, methods, and events, and allows DataSet, EmpProv. Name the ClientDataSet EmpCds.
you to write an application that you can convert to multi-tier
architecture with fewer changes later. The DataSetProvider and ClientDataSet components are required,
because all the dbExpress dataset components provide a unidirec-
Next, return to the dbExpress tab on the Component palette tional read-only cursor, i.e. you cannot move backward through the
and add a SQLDataSet component to the data module. While records and you cannot make changes using dbExpress components.
dbExpress includes three other dataset components, SQLTable, To be able to browse both backward and forward through records
SQLQuery, and SQLStoredProc, these components are provided and insert, delete, and update records, you need the ClientDataSet
as analogs to the BDE dataset components to make conversion of component to buffer and edit the records supplied by the dbExpress
BDE applications to dbExpress easy. The SQLDataSet component dataset component, and the DataSetProvider to generate the SQL
can do everything the other three dataset components can do, statements to insert, delete, and update records in the database.
so there’s no reason to use anything else in a new application.
Begin by setting the SQLDataSet’s SQLConnection property to the The components added so far provide everything necessary to
EmpConn connection component. Next, set the CommandText display information from the Employees table in the top grid
If you don’t want to distribute these two DLLs, you can compile
the code they contain directly into your executable. All you have
to do is add three units, DbExpInt, MidsLib, and Crtl, to the
uses clause in one of your applications’ units, and all the required
dbExpress code will be compiled into your EXE. Bill Todd is president of The Database Group, Inc., a database consulting
and development firm based near Phoenix. He is co-author of four database
Conclusion programming books, author of more than 80 articles, a Contributing Editor
dbExpress makes it easy to create and deploy applications that to Delphi Informant Magazine, and a member of Team Borland, providing
use SQL database servers. Since all of the database connection technical support on the Borland Internet newsgroups. Bill is also a nationally
information is contained in the properties of the SQLConnection known trainer and has taught Delphi programming classes across the country
component, you have complete control over whether this informa- and overseas, and is a frequent speaker at Borland Developer Conferences in
tion is embedded in your application and stored in the registry, an the US and Europe. Bill can be reached at bill@dbginc.com.
By Mike Edenfield
Console Applications
Part II: Advanced I/O
L ast month, in Part I of this series, we covered the basics of writing applications that
make use of the Windows console (character-mode) subsystem. This subsystem
user interface allows you to write command-line utilities and applications that look
and behave like applications written for the MS-DOS operating system.
This is only the beginning of what console applica- Windows console-mode API allows you to take
tions can do, however. Far from being restricted advantage of all the features of the Windows pro-
to old, pre-Windows programming techniques, the tected-mode environment, as well as advanced fea-
tures of the console window that go far beyond
what was possible in MS-DOS.
function ReadConsoleOutput(hConsoleOutput: THandle;
lpBuffer: Pointer; dwBufferSize, dwBufferCoord: TCoord; Low-level Output
var lpReadRegion: TSmallRect): BOOL; stdcall;
The console window’s output consists of a collec-
function WriteConsoleOutput(hConsoleOutput: THandle;
lpBuffer: Pointer; dwBufferSize, dwBufferCoord: TCoord;
tion of character cells. Each cell has a character
var lpWriteRegion: TSmallRect): BOOL; stdcall; and an attribute associated with it. Internally, Win-
function FillConsoleOutputCharacter( dows treats the output as a two-dimensional array
hConsoleOutput: THandle; cCharacter: Char; of CHAR_INFO records. Several low-level output
nLength: DWORD; dwWriteCoord: TCoord;
var lpNumberOfCharsWritten: DWORD): BOOL; stdcall;
functions exist that allow you to act on a range of
function FillConsoleOutputAttribute( these cells at once (see Figure 1).
hConsoleOutput: THandle; wAttribute: Word;
nLength: DWORD; dwWriteCoord: TCoord; The first two functions act on a rectangular region
var lpNumberOfAttrsWritten: DWORD): BOOL; stdcall;
of the screen. The destination buffer pointer is filled
function ReadConsoleOutputCharacter(
hConsoleOutput: THandle; lpCharacter: PAnsiChar;
with an array of CHAR_INFO structures contain-
nLength: DWORD; dwReadCoord: TCoord; ing the cells at the requested region. The remaining
var lpNumberOfCharsRead: DWORD): BOOL; stdcall; functions act on a continuous series of these char-
function ReadConsoleOutputAttribute( acters, returning either an array of characters or
hConsoleOutput: THandle; lpAttribute: Pointer;
nLength: DWORD; dwReadCoord: TCoord;
attributes. Wrapping is automatic at the end of the
var lpNumberOfAttrsRead: DWORD): BOOL; stdcall; line. If the bottom of the screen is reached, the
function WriteConsoleOutputCharacter( functions cease processing and return the number
hConsoleOutput: THandle; lpCharacter: PChar; of characters written in the last var parameter.
nLength: DWORD; dwWriteCoord: TCoord;
var lpNumberOfCharsWritten: DWORD): BOOL; stdcall;
function WriteConsoleOutputAttribute(
Use of these functions is fairly straightforward. The
hConsoleOutput: THandle; lpAttribute: Pointer; sample programs in Listings One and Two (begin-
nLength: DWORD; dwWriteCoord: TCoord; ning on page 14) demonstrate a common use of
var lpNumberOfAttrsWritten: DWORD): BOOL; stdcall; the FillConsoleOutputCharacter function: clearing
the screen. This is done by simply filling the entire
Figure 1: Low-level console output functions. screen, starting at 0,0, and proceeding for (height *
These three assumptions are the defaults for new console applications, Contained within a console are its input and output buffers. Each
but by no means are they the only options. Processes are free to console has a single input buffer that handles all input events. However,
detach themselves from their parent’s console and create their own, there’s no such limit to the number of output buffers, which are referred
as well as create multiple output buffers and various-sized windows to as “screen buffers.” There are two-dimensional grids of characters
into those output buffers. Taking advantage of these features requires representing what’s to be displayed on the console. A single screen buffer,
an understanding of the three different Windows objects involved in the same size as the console window, is created automatically. The API
displaying text to a console. calls to manipulate screen buffers are shown in Figure 3.
The console itself is an abstract object created by Windows, either You can create as many screen buffers as you want, but only one of
in response to a console application being run, or at the request of them can be active at any given time. The various console output
the application. It contains several I/O buffers, and a visible window functions all take a handle to a screen buffer, which doesn’t have to
representing the program interface. The API calls that deal directly be the active screen buffer. The standard output handle retrieved from
with the console are shown in Figure 2. GetStdHandle, however, will always refer to whichever console buffer
is active at the time it’s called.
Unfortunately, the GetConsoleWindow function only works in
Windows 2000. It allows you to get a legitimate window handle It’s interesting to note how changing the active screen buffer
to the console window, and use it with API calls such as affects calls to the library functions, Write and Writeln. These
ShowWindow. In earlier operating systems, you could get this functions act on a global textfile-type variable called Output by
information via EnumWindows or similar functions. To assist you default. This variable is attached to the standard output when
in finding the console’s window handle, you can retrieve (or your application first executes, and never changes. Keep this in
change) the title bar for the console window with the appropriate mind if you change the active screen buffer, as your output from
functions. If you don’t change it, the console window takes on the Write or Writeln may no longer be visible until you switch active
name of the last process to attach to it (such as the EXE name buffers. Note in Listing One the function WriteToScreen, which
of a DOS command). is used in place of Write or Writeln, always writes to the current
active buffer.
For high-level input and output, the following console modes are
available:
ENABLE_LINE_INPUT — Input is processed by your applica-
tion one line at a time.
Figure 4: The example ScreenBuffers program at run time. ENABLE_ECHO_INPUT — Characters are echoed back to the
screen as entered.
ENABLE_PROCESSED_INPUT — Windows handles editing
Attempting to scroll beyond the bottom of the screen buffer in and control characters internally.
NT/2000 actually causes the view window to shrink. This confusing ENABLE_PROCESSED_OUTPUT — Windows handles con-
behavior occurs when you attempt to set the view window position trol sequences automatically.
via relative coordinates, and the bottom coordinate is beyond the ENABLE_WRAP_AT_EOL_OUTPUT — Lines wrap auto-
bottom of the screen buffer. In this case, the top coordinate is matically at the edge of the screen buffer.
adjusted as requested, but the bottom coordinate is left the same,
effectively changing the number of lines in the view window. To By default, all five of these options are turned on. This mode of operation
avoid this problem, use the function GetConsoleScreenBufferInfo, and is sometimes called “cooked” mode, because Windows performs most
either calculate absolute coordinates for the new view window instead of the editing and control-code handling for you. Your ReadFile calls
of relative ones, or verify that your relative coordinates won’t cause will block until a carriage return is entered, and Windows will correctly
this shrinking behavior before setting them. process and echo back any pressed editing or control keys. If you want
to turn off the three input modes, keep in mind that ECHO_INPUT
This odd behavior doesn’t occur in Windows 95/98. In fact, Win- mode is disabled automatically if you disable LINE_INPUT. Also,
dows 95/98 programs cannot change the size of their output buf- attempts to turn off WRAP_AT_EOL_OUTPUT mode under Win-
fer’s view window under any circumstances. Attempting to change dows 95/98 are silently ignored.
the size of a view window will render the attached output buffer
useless: no output will be displayed as long as the view window Low-level console output, using functions such as WriteConsoleOutputCharacter
size differs from the size of the actual console window. To most or FillConsoleOutputAttribute, isn’t affected by any input or output
clearly see this behavior, run Listing One under Windows 95/98, modes. This low-level output is always in a very raw, direct form.
then comment out indicated sections and run it again. As long as However, the ReadConsoleInput API and related low-level input func-
the second screen buffer is a different size than the first, it will tions are affected by these three input modes:
appear empty. In contrast, running the application under Windows ENABLE_MOUSE_INPUT determines if MOUSE_EVENT
NT/2000 will result in the dimensions of the console window input events are reported to your application.
changing as the active screen buffer changes, and the text in all ENABLE_WINDOW_INPUT determines if
buffers will always be visible. WINDOW_BUFFER_SIZE_EVENT input events are reported
to your application.
Notice there’s no {$APPTYPE CONSOLE} directive in Listing ENABLE_PROCESSED_INPUT automatically handles CC.
One because it’s not needed; the console is created by a call to Alloc-
Console. (The run-time result of Listing One is shown in Figure 4.) Processed input mode here simply means that Windows automati-
This is done primarily for demonstration purposes. In general, if you cally calls the control handler when CC is pressed. By default,
know you’ll always have a console while your program is running, processed and mouse input are on, but window input is off. Note
you’ll get better performance by letting Windows create it automati- that no matter what console mode you are in, you’ll always receive
cally. The API call will be used more effectively in later installments KEY_EVENT, FOCUS_EVENT, and MENU_EVENT messages.
of this series, when we create console windows for normal GUI
applications. In practice, you’ll rarely need to change these modes. While turning
off the default input modes for high-level input gives you much
Console Modes finer control over console input, you can achieve the same effect
Much of the behavior of the console window depends on what mode simply by using the low-level input functions. And, as we are about
Windows has put the console in. For most situations, the default to see, it’s a simple task to override the default CC handler to
settings for the console mode will be sufficient. However, changing do what you want.
these modes allows you a very intimate level of control over the
behavior of the console. Changing and querying the console modes is Console Control Handlers
accomplished via two API calls: One of the key elements often mentioned when discussing console
input modes is the console’s control handler. This handler is a func-
function GetConsoleMode(hConsoleHandle: THandle;
tion that Windows calls when certain system-level actions take place
var lpMode: DWORD): BOOL; stdcall; that could affect the console. Windows installs an empty handler that
function SetConsoleMode(hConsoleHandle: THandle; simply calls ExitProcess when the console is created. To install your
dwMode: DWORD): BOOL; stdcall; own, use this API call:
This function will send the specified signal to the console window
associated with the calling process. Any other applications that are
both attached to that console and part of the given process group will
receive this signal. This can be used by a parent process to control the
behavior of multiple child processes at once.
Figure 5: The control-handlers example program at run time.
The sample program in Listing Two only deals with the more basic
behavior of signal handlers. (It’s shown at run time in Figure 5.) We
function SetConsoleCtrlHandler( simply install a pair of control handlers and have them demonstrate
HandlerRoutine: TFNHandlerRoutine; Add: BOOL): the effect of the return values on Windows behavior. As you can see by
BOOL; stdcall;
the behavior of this sample, if none of the other handlers return True
to indicate they’ve handled a given signal, the internal default handler
The HandlerRoutine is a pointer to a function that accepts a single is still executed last. Its behavior is to simply kill the process whenever
DWORD parameter, and returns a BOOL value, called with the stdcall any of the control signals are received. If this isn’t the behavior you
calling convention. The function adds or removes (depending on the want, you must make sure to install a control handler that returns True
value of the Add parameter) your function from the list of registered as soon as possible after your program begins executing.
handlers. In Windows NT/2000, you can also add and remove NULL
as a HandlerRoutine. This special handler is designed specifically to Conclusion
ignore CC; otherwise it passes control to the default handler. In this installment of the console programming series, we went over
the advanced features console applications provide to programmers.
Each handler is called, in the reverse order they were registered, We’ve now covered nearly every aspect of console programming and
whenever a system event occurs. The list of events your handler may the console APIs, and observed how to take very low-level control of
receive in its single DWORD parameter are: the console and its associated buffers.
CTRL_C_EVENT — User pressed CC.
CTRL_BREAK_EVENT — User pressed Cak. However, console programming is most effective when used with
CTRL_CLOSE_EVENT — User attempted to close the console the rest of the Windows API. In the final article in this series,
window. next month, we’ll examine how to use other common Windows
CTRL_LOGOFF_EVENT — User attempted to log off the programming techniques in conjunction with console applications.
system. In particular, we’ll examine how to use a console within a program
CTRL_SHUTDOWN_EVENT — User attempted to shut thread, create a message queue that a GUI-less application can use,
down windows. use consoles and graphical windows in the same application, and
replace the standard input and output handles with handles of our
Each handler has the opportunity to handle the system events on its own, including other file handles and anonymous pipes, to perform
own, or pass them along the chain. If the control handler believes it I/O redirection from within a Windows application. ∆
has successfully handled the event, it should return True. This will
abort the processing of the event. Otherwise, it should return False to The sample programs referenced in this article are available on the
pass control to the next handler in the chain. Delphi Informant Magazine Complete Works CD located in INFORM\
2001\SEP\DI200109ME.
The last three events, the close, logoff, and shutdown messages,
exhibit special behavior depending on the results of the handler
functions. If all of the handler functions return False, the process
will exit as soon as the last handler is done. However, if any of the
handler functions return True, Windows will instead prompt the user
to confirm that the process should be shut down. The user then
has the option to terminate the process immediately, or prevent the
process (and in turn, Windows itself ) from shutting down. Also, if a
handler function takes too long to return during one of these events,
Windows will automatically give the user the option to close the
process, cancel the shutdown, or continue to wait.
procedure SwitchConsole;
program ScreenBuffers; begin
if hActive = hOriginal then
uses begin
Windows, SysUtils; SetConsoleActiveScreenBuffer(hSecond);
hActive := hSecond;
var end
hInput, hOriginal, hSecond, hActive: THandle; else
arrInputRecs : array[0..9] of TInputRecord; begin
dwCount, dwCur : DWORD; SetConsoleActiveScreenBuffer(hOriginal);
hActive := hOriginal;
bQuit : Boolean = False; end;
coorNew : TCoord = (X:100; Y:100); end;
rectView: TSmallRect =
(Left:0; Top:0; Right:99; Bottom:49); begin
{ First, release our existing console,
const and make a new one. }
OUTPUT_STRING = FreeConsole;
'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()[]{}'; AllocConsole;
FOREGROUND_MAGENTA = FOREGROUND_RED or FOREGROUND_BLUE; SetConsoleTitle('Screen Buffers Demo');
FOREGROUND_BR_MAGENTA = { Get a handle to the auto-created input
FOREGROUND_MAGENTA or FOREGROUND_INTENSITY; and output buffers. }
hInput := GetStdHandle(STD_INPUT_HANDLE);
procedure WriteToScreen(buf: String); hOriginal := GetStdHandle(STD_OUTPUT_HANDLE);
var hActive := hOriginal;
dwCount: DWORD;
begin SetConsoleTextAttribute(hActive, FOREGROUND_BR_MAGENTA);
buf := buf + #13#10; WriteLn('Got Standard Output Handle.');
WriteConsole(hActive, PChar(@buf[1]), Length(buf), { Create a second screen buffer. }
dwCount, nil); hSecond := CreateConsoleScreenBuffer(GENERIC_READ or
end; GENERIC_WRITE, 0, nil, CONSOLE_TEXTMODE_BUFFER, nil);
procedure FillOutputBuffer(hBuf: THandle); { *** Windows 95/98: Comment out from here... }
var if not SetConsoleScreenBufferSize(hSecond, coorNew) then
dwAttr, dwCur: DWORD; WriteLn('error: SetConsoleScreenBufferSize() == ',
begin GetLastError)
dwAttr := 0; else
for dwCur := 0 to 100 do begin WriteLn('Adjusted secondary screen buffer size.');
SetConsoleTextAttribute(hSecond, dwAttr + 1);
WriteConsole(hSecond, PChar(@OUTPUT_STRING[1]), if not SetConsoleWindowInfo(hSecond, True, rectView) then
Length(OUTPUT_STRING), dwCount, nil); WriteLn('error: SetConsoleWindowInfo() == ',
dwAttr := (dwAttr + 1) mod 15; GetLastError)
end; else
Writeln('Secondary buffer populated with data.'); WriteLn('Adjusted secondary screen buffer window.');
end; { *** ...to here. }
SetConsoleCursorPosition(hStdOutput, coorHome);
SetConsoleTextAttribute(hStdOutput, FOREGROUND_BR_CYAN);
Write('<1> Control Handler 1 Status: ');
SetConsoleTextAttribute(hStdOutput, FOREGROUND_BR_RED);
if bHandler1 then
WriteLn('ON ')
else
WriteLn('OFF');
SetConsoleTextAttribute(hStdOutput, FOREGROUND_BR_CYAN);
Write('<2> Control Handler 2 Status: ');
SetConsoleTextAttribute(hStdOutput, FOREGROUND_BR_RED);
if bHandler2 then
WriteLn('ON ')
else
T here is a powerful feature of the Delphi and Kylix Code editor that permits you to
add your own custom keystrokes. This little-known feature is referred to as custom
key bindings, and is part of the Open Tools API. The Open Tools API (OTA) provides
a collection of classes and interfaces you can use to write your own extensions to
the Delphi and Kylix IDE.
This article gives you an overview of this interest- line in the Code editor. This is a feature I have
ing feature, and provides a simple key binding class had in other code editors, and now, through key
that you can use as a starting point for creating bindings, in Delphi and Kylix.
your own custom key bindings. This key binding
class, which I am calling the duplicate line key Another interesting thing about this key binding
binding, makes a duplicate or copy of the current is that the DupLine.pas unit, which you can down-
load (see end of article for details), can be installed
and used in Delphi 5, Delphi 6, and Kylix.
IOTAKeyboardBinding = interface(IOTANotifier) As you can see, this interface declares four methods and three
['{F8CAF8D7-D263-11D2-ABD8-00C04FB16FB3}'] properties. Your key binding class must implement the methods.
function GetBindingType: TBindingType; Note, however, that it does not need to implement the properties.
function GetDisplayName: string;
(This is a regular source of confusion when it comes to interfaces,
function GetName: string;
procedure BindKeyboard( but the fact is that the properties belong to the interface and
const BindingServices: IOTAKeyBindingServices); are not required by the implementing object. Sure, you can imple-
ment the properties in the object, but you don’t have to. I didn’t
property BindingType: TBindingType read GetBindingType; in this example.)
property DisplayName: string read GetDisplayName;
property Name: string read GetName;
end; In addition to the methods of the IOTAKeyboardBinding interface,
your key binding class must include one additional method for
Figure 2: The declaration of the IOTAKeyboardBinding interface each custom keystroke you want to add to the editor. To be
in the ToolsAPI unit. compatible with the AddKeyBinding method used to bind these
additional methods, the methods must be TKeyBindingProc type
type methods. The following is the declaration of the TKeyBindingProc
TDupLineBinding = class(TNotifierObject, method pointer type, as it appears in the ToolsAPI unit:
IOTAKeyboardBinding)
private
public TKeyBindingProc = procedure (const Context: IOTAKeyContext;
procedure DupLine(const Context: IOTAKeyContext; KeyCode: TShortcut; var BindingResult: TKeyBindingResult)
KeyCode: TShortcut; of object;
var BindingResult: TKeyBindingResult);
{ IOTAKeyboardBinding }
function GetBindingType: TBindingType;
This declaration indicates that the additional methods you write, each of
function GetDisplayName: string; which adds a different keystroke combination to the editor, must take
function GetName: string; three parameters: IOTAKeyContext, TShortcut, and TKeyBindingResult.
procedure BindKeyboard(const BindingServices:
IOTAKeyBindingServices);
Figure 3 shows the key binding class declared in the DupLine.pas unit.
end;
This class, named TDupLineBinding, includes only one new key binding.
Figure 3: Type declaration of the TDupLineBinding class.
Implementing the Interface
Once you’ve declared your key binding class, you must implement
Creating and installing an editor key binding involves a number of the four methods of the IOTAKeyboardBinding interface, as well
explicit steps: as each of your additional TKeyBindingProc methods. Fortunately,
1) Descend a new class from TNotifierObject. This class must be implementing the IOTAKeyboardBinding interface is easy.
declared to implement the IOTAKeyboardBinding interface. This
class is your key binding. Implement GetBindingType by returning the type of key binding that
2) In addition to the four methods of that IOTAKeyboardBinding you’re creating. There are only two types of key bindings: partial and
interface you must implement in your key binding class, add complete.
one additional method for each key combination you want to
add to the editor. This method is passed an object that imple- A complete key binding defines all the keystrokes of the editor. You can
ments the IOTAKeyContext interface. Use this object within your identify your key binding as a complete key binding by returning the
implementation to read about and control the behavior of the value btComplete. For example, the New IDE Emacs key mapping that
editor. ships with Kylix and Delphi 6 is defined by a complete key binding.
3) Declare and implement a stand-alone Register procedure. Within Fortunately, Kylix ships with the source code for this key mapping,
this procedure, invoke the AddKeyboardBinding method of the which is quite useful if you want to examine a complicated key binding.
BorlandIDEServices object, passing an instance of the class you
declared in the first step as the only argument. A partial key binding is used to add one or more keystrokes to the
4) Add the unit that includes this Register procedure to an installed key mapping you’re using. The TDupLineBinding class is a partial
design-time package. key binding. Here’s the implementation of GetBindingType in the
TDupLineBinding class:
Each of these steps is discussed in the following sections. As men-
tioned earlier, these steps will define a new key binding that adds a function TDupLineBinding.GetBindingType: TBindingType;
single key combination. Once implemented and installed, this key begin
Result := btPartial;
combination will permit you to duplicate the current line in the
end;
editor by pressing CD.
Declaring the Key Binding Class You can implement GetDisplayName and GetName to provide the editor
The class that defines your key binding must descend from with text descriptions of your key binding. GetDisplayName should
TNotifierObject and implement the IOTAKeyboardBinding inter- return an informative name that Kylix will display in the Enhancement
face. Those familiar with interfaces will recall that when a class modules list on the Key Mappings page. On the other hand, GetName is
is declared to implement an interface, it must declare and imple- a unique string the editor uses internally to identify your key binding. By
ment all the methods of that interface. Consider the declaration of convention, this name should be your company name or initials followed
the IOTAKeyboardBinding interface (see Figure 2), which appears by the name of your key binding, because this string must be unique for
in the ToolsAPI unit. all key bindings a user might install.
AddKeyBinding requires at least three parameters. The first is an array Set the BindingResult formal parameter of your TKeyBindingProc method
of TShortcut references. A TShortcut is a Word type that represents to krHandled if your method has successfully executed its behavior. Set-
either a single keystroke, or a keystroke plus a combination of one ting BindingResult to krHandled also has the effect of preventing any
or more of the following: C, A, or S, as well as left-, other key bindings from processing the key, as well as preventing menu
right-, middle-, and double-mouse clicks. Because this parameter can items assigned to the key combination from processing it.
include an array, it’s possible to bind your TKeyBindingProc to two or
more keystrokes or key combinations. Set BindingResult to krUnhandled if you don’t process the keystroke or
key combination. If you set BindingResult to krUnhandled, the editor
The Menus unit in Delphi, and the QMenus unit in Kylix, contain will permit any other key bindings assigned to the keystroke or key
a function named Shortcut that you can use to easily create your combination to process it, as well as any menu items associated with
TShortcut references. This function has the following syntax: the key combination.
function Shortcut(Key: Word; Shift: TShiftState): Set BindingResult to krNextProc if you have handled the key, but want to
TShortcut; permit any other key bindings associated with the keystroke or key com-
bination to trigger as well. Similar to setting BindingResult to krHandled,
where the first parameter is the ANSI value of the keyboard character, setting BindingResult to krNextProc will have the effect of preventing
and the second is a set of zero, one, or more as TShiftState. The menu shortcuts from receiving the keystroke or key combination.
19 September 2001 Delphi Informant Magazine Delphi Informant Magazine September 2001 19
In Development
4) Set the Package file name to the name you want to give your
package. In Figure 6, this file is named keybin.dpk, and is
being stored in the \Lib directory where Delphi 6 is installed.
(In Linux, a good place to store this file is the hidden .Borland
directory, located in the user’s home directory.) Also provide
a brief description for the package in the Package description
field.
5) Click OK. A dialog box is displayed to inform you that it will
build the package and install it.
6) Click Yes. After creating, compiling, and installing the pack-
age, you will see a dialog box confirming that the package is
installed.
7) Click OK. The installed package now appears in the Package
editor, as shown in Figure 7.
8) Close the Package editor, making sure to click Yes when it asks
Figure 7: The Package editor. if you want to save changes to the package project.
Your key binding is now available. It will now appear in the Key
Mappings page of the Editor Properties dialog box, as shown in
Figure 8. Your installed key binding is now available to all editor
key mappings. If you want to disable your key binding, uncheck
the check box that appears next to its name in the Enhancement
modules list box.
Conclusion
As you’ve learned in this article, key bindings are part of the
Open Tools API that permit you to add custom keystrokes to the
Delphi/Kylix Code editor. The Open Tools API is just one of many
reasons that Delphi and Kylix are the best tools on the market. ∆
Figure 8: The newly installed key binding appears on the Key
Mappings page of the Editor Properties dialog box. The file referenced in this article is available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2001\SEP\
Invoke this method by dynamically binding the BorlandIDEServices DI200109CJ.
reference to the IOTAKeyboardServices interface, and pass an invoca-
tion of your key binding object’s constructor as the argument. The
following is how the Register procedure implementation appears in
the DupLine unit:
procedure Register;
begin
(BorlandIDEServices as IOTAKeyboardServices).
AddKeyboardBinding(TDupLineBinding.Create);
end;
By Alex Fedorov
D elphi has a long history of database support, from the BDE in Delphi 1 and subsequent
versions, to the ADOExpress components that implement the ADO- and OLE DB-based
data access in Delphi 5 and 6. Now, with the XML features of Microsoft SQL Server 2000,
we can talk about a third generation of data access in Delphi — one that’s compatible
with main IT trends.
Last month, you were introduced to the new fea- main topic of how to query SQL Server 2000 for
tures of Microsoft SQL Server 2000 that make it XML data from Delphi.
an XML-enabled database server, e.g. the ability
to access SQL Servers using the HTTP protocol, Delphi and the Microsoft XML Parser
support for XDR (XML-Data Reduced) schemas, Before we can use the Microsoft XML Parser, we
the ability to specify XPath queries against these need to create an interface unit that will encapsu-
schemas, and the ability to retrieve and write XML late all objects and methods implemented in this
data. You also learned that XML-based data extrac- COM object. To do so, select Project | Import Type
tion is implemented in SQL Server 2000 by the Library in the Delphi IDE. Then, in the Import
FOR XML clause that’s part of the SELECT state- Type Library dialog box, select the Microsoft
ment. The ability to query Microsoft SQL Server XML type library. The version you have installed
directly — without extra data access components on your computer depends on the software you
— and receive data represented as XML documents have, but it’s recommended to have the latest ver-
opens new possibilities for Delphi developers. sion of the Microsoft XML Parser that’s available
free from the Microsoft Web site. As of this writ-
To parse XML documents generated by querying ing, the most recent version is 3.0.
SQL Server, we’ll naturally need a parser. And since
we’re using a Microsoft database, the best choice After selecting the type library, uncheck the
is the Microsoft XML Parser, which provides a lot Generate Component Wrapper check box, and click
of objects, methods, and properties to manipulate Create Unit. The SXML2_TLB.PAS file will be
XML documents. So we’ll begin this month’s discus- created and added to your project. Later, you’ll
sion by describing how Delphi interacts with the need to save it. The usual place for imported type
Microsoft XML Parser, before moving on to the library interface units is the \Lib folder (under
\Delphi5 for example).
Old Name New Name Where
type type_ IXMLDOMNode.nodeType It’s always wise to take a look at the generated Pascal
implementation implementation_ IXMLDOMDocument code, especially when you’re familiar with the objects
type type_ IXMLDOMDocument.createNode and methods of the COM object, or you’re planning
var var_ IXMLDOMSchemaCollection.add to port the existing code to Delphi. At the top of
type type_ IXMLElement the file in the commented “Errors” section, we can
type type_ IXMLElement2 find the changes in method, property, and parameter
Figure 1: Delphi changes some method, property, and parameter names performed by Delphi. In our case, we should
names when it imports the Microsoft XML Parser type library. be aware of the changes shown in Figure 1.
var var
HTTP : IXMLHTTPRequest; HTTP : IXMLHTTPRequest;
XMLDoc : IXMLDomDocument; XMLDoc : IXMLDomDocument;
... ...
22 September 2001 Delphi Informant Magazine Delphi Informant Magazine September 2001 22
Columns & Rows
As you can see from using the XML property of the XMLDOMDocument
object, we can easily access the entire XML document. Let’s look at how
we can use the XML document in our Delphi applications.
Figure 10: One row of data is shown in the Memo component.
Using RAW Mode
As we already learned last month, in the RAW mode we receive the
XML document, where each row of the resulting recordset is stored Next, extract the table’s column names. To do so, iterate the Attribs
with the generic <row /> element, and each column is mapped to collection (of the IXMLDOMNamedNodeMap type) of the first child
an attribute of this element. The attribute name equals the name node of the XML document, and extract the names of each node in
of the column. this collection (see the first for loop in Figure 7).
Let’s see how to process this document and display its contents in a Now we can show the data. This is done with a similar technique, as
ListView component. First, load the XML document that results from shown in the second for loop in Figure 7, but this time we iterate all
the following query: child nodes of the XML document. The result of our manipulations
is shown on another example form, containing a Button and ListView
SELECT * FROM Customers FOR XML RAW component (see Figure 8).
Then take the root node of the XML document, and access its attri- To study the resulting XML representation for just one row of data, let’s
butes node: add a Memo component to the form. Then enter the code shown in
Figure 9 for the OnClick event handler for the ListView component.
Root := XMLDoc.DocumentElement; Now, when we run our example and click on a row, we’ll receive its
Attribs := Root.FirstChild.Attributes; XML representation, as shown in Figure 10.
This was a very simple example of how to show XML-based data in The projects referenced in this article are available on the Delphi Infor-
Delphi applications. We’ve assumed that all records have columns filled mant Magazine Complete Works CD located in INFORM\2001\SEP\
with information, but if we look at some of the records, we’ll find that DI200109AF.
not all of the columns are represented in the resulting XML document.
This is because when some columns have Null as their content, they
aren’t presented in the XML document.
24 September 2001 Delphi Informant Magazine Delphi Informant Magazine September 2001 24
Kylix Tech
Linux / Kylix / fork / execv / Environment Variables
By Brian Burton
The Problem: Finding the Libraries When a program requires shared libraries installed
Applications typically install their shared libraries in outside of /usr/lib, users generally need to set an envi-
one of these locations: ronment variable, LD_LIBRARY_PATH, to include
A standard system directory that’s automatically these directories. LD_LIBRARY_PATH is a colon-
searched by the system for shared libraries, such separated list of directory names for the Linux loader
as /usr/lib. to search when looking for shared libraries.
The directory containing the application’s execut-
able. For example, suppose your application is called Foo-
A directory named “lib” under the application’s bler and is installed in the directory /usr/local/
directory. With this option the application’s exe- Foobler. Further suppose that the application uses
cutables are generally stored in a directory named several shared libraries that are all installed in the
“bin” under the application’s root directory. Foobler directory. Any user that wants to run
Foobler will need to modify his or her shell init file
Copying the shared libraries into the /usr/lib directory (.profile for bash or ksh, .login for csh) to define
has the advantage of simplicity. Once the library has LD_LIBRARY_PATH with the Foobler directory. If
been installed, all users can launch the application the users are using bash (the default shell for new
there without adjusting any of their environment vari- accounts on many Linux distributions) they will need
ables. However, there are also some potential draw- to add the following to their .profile file:
backs:
Only the root user is allowed to copy files into LD_LIBRARY_PATH=/usr/local/Foobler:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
/usr/lib. Often, users other than the root will
want to install and use the application without
involving a system administrator. Updating the .profile file for every new application
Other applications that install libraries into /usr/ can be tedious, and difficult to explain to new Linux
lib might overwrite the application’s shared librar- users. Many applications (including Kylix itself) work
ies with incompatible versions. around this problem by installing a “launcher” script
that adjusts environment variables before starting the
To allow users other than the root to install and application. The primary tasks for a launcher program
update your application, the best option is to install are to:
Updating the Environment The Libc unit provides a family of related procedures for invoking
Once the application directory has been discovered, the PATH and the execve system call. Before calling execve, each procedure accepts
LD_LIBRARY_PATH environment variables can be updated within slightly different parameters and performs some special processing.
the launcher’s own environment using the GetEnvironment function The launcher example uses the execv procedure, which accepts the
from the System unit, and the setenv procedure from the Libc unit, absolute path to the executable file to launch and the complete set of
as shown in Figure 2. command-line arguments for the program.
Adding the application directory to the PATH environment variable The Kylix System unit exposes the command-line arguments to the
isn’t strictly necessary. Doing so, however, allows the application to application as the platform-specific global variables, ArgCount and
more easily launch other executables in its own directory. ArgValues. ArgCount contains the number of command-line argu-
ments in ArgValues. ArgValues is a pointer to an array of PChars with
Launching the Application ArgValues+1 elements. The first element of the array contains the path
Finding the program to launch and updating the environment both to the executable file of the running program. The last element of the
var
target_name : string;
target_dir : string;
begin
FindTarget(target_name, target_dir);
SetPath('LD_LIBRARY_PATH', target_dir);
SetPath('PATH', target_dir);
ExecTarget(target_name);
{ We only reach this point if execv fails. }
Writeln(ErrOutput, 'error: unable to execute ' +
target_name);
Writeln(ErrOutput, Format(
'code=%d msg=%s', [errno, strerror(errno)]));
end.
array is a nil pointer to indicate the end of the array. The elements
in between (if any) are the additional command-line arguments used
to launch the program.
To launch the application, the sample launcher simply copies its own
ArgValues array (see Figure 3) and changes the first element to refer
to the program being launched. Then it calls execv to actually launch
the program. Replacing the first element in ArgValues isn’t strictly
necessary, but by doing so the launched application can easily identify
its own executable file, and is completely oblivious to the fact that it
was started by the launcher, rather than the user’s shell.
Possible Improvements
The sample launcher could be extended in any number of ways to
make it more useful for a given application. For example, if the applica-
tion stores its executable files in a bin directory, and its shared libraries
in a lib directory, the launcher could be modified to set the PATH
and LD_LIBRARY_PATH to different values, thus:
By Alessandro Federici
Introduction to COM
The Basic Building Blocks of Windows Development
A few months ago, while looking for my first house, I spent quite a lot of time
with a real estate agent. This taught me the three most important aspects of that
business: location, location, location. With the Component Object Model (COM), the
reverse is true; location is ideally the last thing in which you are interested. Instead,
the mantra is integration, integration, integration.
Every Windows user, whether aware of it or not, ment to HTML or RTF. The integration of all these
deals with COM every day. COM is used by parts will constitute your word processor.
Microsoft Office when we run the spell-check util-
ity, by many Web sites running IIS, and by the Now, imagine that you want to be able to update
operating system for some of its mundane tasks. any of these single elements without redeploying the
Some developers choose COM specifically to build whole application. You may also want to make those
complex, scalable, and secure enterprise systems. components available to a second application — per-
haps developed by someone else in another language.
Integration Yesterday These are common scenarios. Today’s applications are
In our field, integration can be accomplished in much bigger than those of a few years ago, so any-
many ways, and it can be applied to many things. thing that can help tame this complexity is welcome.
Imagine you are developing a word processor appli-
cation. You will create or inherit a custom memo The first approach you may try is to use DLLs.
control for editing purposes, you may include a Dynamic-link libraries allow us to bind to and
spell checker, and — if you want to get fancy invoke executable code at run time. In our hypo-
— you may also want to include a set of custom thetical word processor, the DLL that includes
routines that allows your users to convert the docu- the conversion routines might export routines and
structures such as these:
TConverter = record
ID : Integer;
Name, FileExtension, Description: string;
end;
Figure 1: Delphi’s Type Library editor allows us to view and edit This works fine and you will achieve your objec-
COM-type libraries. tive. GetConverters returns a custom TList (TCon-
verterList) which may have some Delphi methods that make it handy Let’s take a look. In Delphi, open the COMConverter.tlb type library
and easy to use. contained in the \COMConverter directory (created by the example
application). As you can see in Figure 1, this type library defines the
Unfortunately this isn’t an optimal solution. Worse, it doesn’t work unless IConverterList interface, which contains the Count and Items properties.
you’re using Delphi or Borland C++Builder. To use the result of the
function GetConverters pointer as a TConverterList, the client needs to Delphi knows how to interpret type libraries, and present them in a
know what a TConverterList is. To do this, you need to share that user-friendly fashion via its Type Library editor. Microsoft Visual Basic,
information. Even if you do, however, you’d have a problem with non- Borland C++Builder, or Microsoft Visual C++ do the same. They all
Borland compilers. TList is a VCL class. It’s not included, for instance, in support the COM binary standard and play according to its rules. Now,
Microsoft Visual C++ or Visual Basic; developers in those environments from within the Type Library editor, press @. Delphi will create a file
couldn’t benefit from the pointer we return. named COMConverter_TLB.pas (see Figure 2).
You could have structured your DLL differently, following, for exam- Through COM, I can define an interface that other COM-enabled
ple, the approach of the Windows API function, EnumWindows, languages can understand. Delphi will use that information to generate
which takes a pointer to a callback routine. Another solution would interfaces that it can understand and use, as we just saw. It’s all there and
have been to export more functions. Whichever approach you may ready to be used, as if it were a regular Delphi object. There are some key
choose, however, you’d still be confined to a world of simple data differences, but let’s continue with the principles.
types that is everything but object-oriented. On top of that, the DLL
has to be run on the client’s computer. Dynamic Linking
Similar to DLLs, COM allows — and actually only works through —
Integration Today dynamic linking. You can choose to take advantage of this in two ways:
COM is one of the technologies that helps us resolve some of these early binding or late binding.
issues. COM has a long story. Officially, the acronym COM was first
used around 1993. We can trace COM roots back to Windows 3.x Before we continue, you need to register your COM library. Registration
where DDE and OLE were used in Microsoft Word and Excel is the process through which Windows becomes aware of a COM object
as a sort of rudimentary communication and interoperability glue. and learns how to instantiate it. To do this, you need to use a special tool
Today COM is everywhere on the Windows platform. Small applica- called REGSVR32.EXE (contained in Windows\System32), or Borland’s
tions such as ICQ, CuteFTP, or Allaire HomeSite, are accessible equivalent tregsvr.exe (in \Delphi5\Bin for example). Another way, if you
through COM. Application suites such as Microsoft Office are based have the Delphi source code, is to open the COM project (in our case
on COM. Windows-based enterprise systems leverage COM and COMConverter.dpr) and select Run | Register ActiveX Server.
Microsoft Transaction Server for business-critical operations. If you
develop on Windows, you will have to face COM sooner or later. The Registering a COM server means inserting special keys into the
sooner you do, the easier it will be. Windows registry. The information you will store includes the
name of the DLL or EXE file that hosts your COM object, the
This article is about understanding COM and the reasons it’s impor- identifiers that uniquely identify it (see the yellow on green code
tant, rather than providing another how-to tutorial. We’ll start with in Figure 2) and a few other items. If you don’t do this, Windows
the principles behind COM, then provide a concrete example you won’t be able to instantiate your COM object.
can download and examine for yourself (see end of article for
details). The first part won’t take long, but will give you a better Continuing our analogy to DLLs, early binding is similar to import-
understanding of what happens in the example and why. ing routines from a DLL by using the external directive. When you
Late binding is similar to the GetProcAddress API call in which you procedure TForm1.bLateBindingClick(Sender: TObject);
specify the name of the function you want to connect to using a var myobj: OleVariant;
string, and are returned a pointer. When you do that, your client runs begin
myobj := CreateOLEObject('COMCOnverter.ConverterList');
fine, unless you try to use the function with the wrong parameters.
ShowMessage('There are ' + IntToStr(myobj.Count) +
' converters available');
Invoking methods of a COM object through early binding is end;
faster than using late binding. Every time you use late binding,
you’re asking Windows to look for a method called with a certain procedure TForm1.bEarlyBindingClick(Sender: TObject);
var
name, return a pointer to it, and then invoke it. Using early myobj : IConverterList;
binding means you immediately call it, without any additional begin
overhead, because you already know the location of that method’s myobj := CoConverterList.Create;
entry point. However, using late binding allows much more flex- ShowMessage('There are ' + IntToStr(myobj.Count) +
' converters available');
ibility, and makes possible things such as scripting.
end;
As an example, this is the content of the VBTest.vbs file contained Figure 3: Use an OleVariant variable for late binding, and a
in the example application’s \WordProcessor directory: typed variable for early binding.
Dim MyObj, i, s We have seen how COM lets us use objects embedded in DLLs.
Set MyObj = CreateObject("COMConverter.ConverterList") Wouldn’t it be nice if, on top of that, those DLLs could be located
s = ""
and executed on a more powerful machine? Wouldn’t it be nice not to
For i = 0 To (MyObj.Count-1)
s = s & MyObj.Items(i).Description & ", " have to worry about TCP/IP communication and sockets? This is all
Next possible using Distributed COM (DCOM).
MsgBox("You can save as " & s)
DCOM is an extension of COM that allows inter-process com-
Double-click on it and see what happens. Our ConverterList munication across machine boundaries. Using DCOM, the only
object will be created and the names of all supported converters thing that changes for the developer is the way you instantiate
will be displayed, all without Delphi, VB, or anything else. This your COM object. Instead of calling CoCreate, you would now call
is done using a late-bound call to the methods Get_Items and CoCreateRemote(<ServerName>), passing either an IP address or the
Get_Count. The ActiveX Scripting engine embedded in Windows name of the machine that executes the COM object.
(which is also accessible through COM) took care of parsing the
text file, and asking to find and invoke it. When you do this, Windows creates an object (proxy) on the
client machine that looks exactly like the real object. When you
You can do the same in Delphi, but how can you make sure you’re call a method on the proxy, it takes care of delivering your call
using one instead of the other? That’s easy. The way to do late and the parameters you specified to the other machine where a
binding in Delphi is generally by using OleVariant variables. By listener (stub) is waiting. The stub then invokes the real method
using typed variables, you’re using early binding. Figure 3 shows a and packages back the result. All this is done transparently for
snippet of code from the fMainForm unit in the \WordProcessor you. Codewise, the only difference for you is to specify CoCreate
directory. As you can see, the only differences between the two are or CoCreateRemote when creating your COM object.
the type of myobj and the way it’s instantiated.
Conclusion
The fact that you declared myobj as an OleVariant is the key here. COM is an excellent technology for developing flexible, expandable,
That tells Delphi how you invoke the methods of a COM object. and open Windows applications. It defines a standard object-oriented
Any time you use an OleVariant you can specify any method name. way of exposing functionality and promotes integration. By embrac-
The compiler won’t complain. Try putting myobj.XYZ in the first event ing COM you can make your application more open, expandable,
handler. It will compile successfully, but at run time Delphi will raise and controllable (whenever needed) from the outside world. You will
an exception as soon as you hit that line of code. In the second have access to a wide set of tools and functionality embedded in your
case, you wouldn’t be able to compile it, because IConverterList doesn’t operating system, and other applications such as Microsoft Office,
define a method named XYZ. which will enhance the functionality you can provide.
Location Independence If you need to develop enterprise systems, you’ll be able to leverage
Not too many years ago the terms “distributed” and “thin client” your investment in this technology and get access to another set of
became very popular. The two terms are often used together when tools and servers (e.g. Microsoft Transaction Server, BizTalk, Applica-
discussing systems physically split into presentation, business, and data tion Center) that won’t require a switch in language or approach.
storage tiers (multi-tier or three-tier systems). Physically split means
that each of those tiers can be running on the same machine, or If you weren’t familiar with COM, I hope this article provided some
on separate ones. In brief, multi-tier architecture was conceived to interesting information to get you started. If you are already using
produce cleaner designs and enhance scalability. However, it’s a topic COM, I hope it helped you understand a little better why COM
unto itself and outside the scope of this article. exists and when you can benefit from using it. ∆
Resources
If you want to read more, I recommend the following introductory books:
Understanding COM+ by David S. Platt, ISBN: 0-7356-0666-8,
Microsoft Press, 1999
Inside COM (Programming Series) by Dale Rogerson, ISBN:
1-57231-349-8, Microsoft Press, 1996
COM and DCOM: Microsoft’s Vision for Distributed Objects by
Roger Sessions, ISBN: 0-471-19381-X, John Wiley & Sons, 1997
Originally from Milan, Alessandro Federici is a recent Chicago transplant. He’s been
developing software since he can remember and has used Borland tools since
Turbo Pascal 3.02. He’s spent the last four years working extensively with the
technologies included under the Microsoft Windows DNA umbrella (COM, DCOM,
COM+, MTS, MSMQ, ASP, IISS, SQL Server, etc.), and developing distributed
systems with InterBase and Oracle as well. He’s worked as a consultant and
has been the Localization Software Engineer on a number of Microsoft projects
(Milan and Dublin). In the US, he’s worked as a developer and system architect
for companies in the financial, warehousing, and supply chain management busi-
nesses. He is a system architect and team leader for the development division of
eCubix. You can contact Alex at alef@bigfoot.com, or through his Web site at
http://www.msdelphi.com.
By Brian Phillips
DirectDraw Delphi
Strategies for Working with Large Bitmaps
Sample Code
The sample code included here used a 65MB sat-
ellite picture of the United States (see Figure 1)
as a test image. DirectDraw refused to create a sur-
face this size. This discussion is about how to load
and display these large images without running into
DirectDraw roadblocks — and do it in a way that
preserves the smooth navigation of the source image
Figure 1: This satellite image of the United States is simply too big for DirectDraw. that large images inherently need.
ROOT
Example Form
The example application will set up a DirectDraw surface, and navigate
the background image based on a focus point. [The application is avail-
able for download; see end of article for details.] All image manipulation
takes place in the TQuadSurface object (see Figure 2). The only duties
for the form are to respond to navigation, to instantiate the DirectDraw
NE SE SW NW
objects, to initialize the variables, and to actually call for a rendering.
The second rectangle is the area to sample from the original TQuadSurface and Setting up the Bitmap
large bitmap. Defining a focus point (usually x and y) within the The sample code implements a form implementation for a user interface.
bounded area of the original image is an easy way to keep track of It also supplies the back buffer for quick double-buffered blitting between
where the view is located. As long as the focus is confined to the surfaces. The divide-and-conquer and culling techniques are imple-
bitmap, a visible transfer will occur. mented in a separate object owned by the front form. The TQuadSurface
encapsulates all of the image processing and rendering techniques into a
There will be occasions when the focus will reveal the edges of the recursively defined object tree.
bitmap. In those situations it may be prudent to define a perimeter
zone surrounding the bitmap where the focus won’t be allowed. This The TQuadSurface class implements the divide-and-conquer algo-
zone (usually half of the display area width on each side, and half rithm onto an arbitrary bitmap, and stores the subsections into
the display area height for top and bottom) will stop corners from a tree for quick retrieval. It recursively samples each part of the
overlapping on the screen. bitmap, and when the proper size is reached, stores the image onto
a DirectDraw surface at a leaf node. Recall that leaf nodes are
During the sampling operations, it’s important to remember that nodes with no children. In this data structure, the only nodes that
the original bitmap that was sampled is no longer in memory. have reference to DirectDraw surfaces are the leaf nodes. Branch
Instead, it has been broken into smaller pieces and copied onto nodes only contain the bounding information used for finding the
individual DirectDraw surfaces. The pieces in each leaf of the proper leaf nodes.
containing tree still have the bounds property that was assigned
during the split, which defined their original coordinates within Each node of the TQuadSurface has reference to four children
the original image. These bounds rectangles will be used to sample nodes (see Figure 4). These are the nodes that make up the
the leaf surfaces and create the back-buffered image. northeast, northwest, southeast, and southwest areas of the rect-
If the rectangle is larger than the maximum size needed to create The general problem is that not all the surface really needs to be
a DirectDraw surface, it must be broken into four parts. The copied. Only the part of the image that actually intersects is neces-
midpoints of the rectangle are found, along with rounding infor- sary. The DirectDrawClipper references attached to the surfaces will
mation. Rounding operations are crucial in these types of applica- clean up any overlap, but often it’s easier to do it within the code.
tions; they can generate strange lines or offsets within the bitmap.
The easy solution used here is to let the rectangles overlap by a The ratio approach to solve this is based on finding a point expressed
single pixel. as a fraction along a line segment. A point along a line segment can
be expressed as a ratio between 0 and 1, multiplied by the length
Four new TQuadSurface objects are created from the area. Each of the line segment referenced from the lower bound of the line
calls its own SetUpPic with the four new areas generated by the segment. For example: Take line 1 that starts at 10 and ends at 30.
bounds of our new regions. This continues until the subdivided Point20 can be expressed as 0.5*(length of the line)+ the lower corner
rectangles are within the bounds of the maximum size, where they of the line. Since the length of the line is 30-10, the point 20 can also
call GetSurface and return to their calling object. be expressed as 0.5*(30-10) +10=20.
GetSurface is the function that actually samples the rectangle of The trick comes when projecting that point onto another line. It
interest from the source bitmap. It first creates a new reference can be done easily by multiplying the ratio across the new basis and
to the IDirectDrawSurface from the clients IDirectDraw that was adding any offset. For example: suppose the value of 20 on line 10
passed into the Init function. Then it uses a few Windows API to 30 needs to be transformed to a line that starts at value 100
calls such as BitBlt to move the bits from the source image onto and ends at value 200. In order that the point 20 (from 10 to 30)
the destination IDirectDrawSurface. It returns the new surface be transformed so its new line is from 100 to 200, the same ratio
reference to the calling function. operation takes place that took place previously.
Remember that when the form wants to display a portion of the Let Point20 equal the value of 20 on a line from 10 to 30, when
image, it passes a back buffer to be filled with a subsection of the projected onto a line form 100 to 200 is equal to the ratio multiplied
original image. To copy a sub-area of the original bitmap onto the by the line length, and then add the lower coordinate of the line:
back buffer, DrawOnSurfaceRect is called. This uses the same recur-
sion methodology that the SetUpPic method used earlier. It checks Point20 = 0.5 (length of line 10 to 30) + the lower bound of 10.
for an intersection between the current node’s bounds, and the area
that should be sampled from the original area of the sampled image. When the line is transformed to 100 to 200, Point20 maintains its
If an intersection is found, then the child nodes are recursively ratio of 0.5 and is now multiplied by the new line magnitude of
called until the leaf branches that contain an intersecting bounds (200-100) and the lower bound of 100 is added to that. Therefore,
area and an instantiated IDirectDrawSurface are found. the transformed Point20 = 0.5*(200-100)+100 =150.
Once the intersection area of the leaf node is found, the area that needs This applies directly to what DrawOnSurfaceRect does. An intersec-
to be copied from the leaf node’s surface onto the back buffer must tion rectangle is found from the bounds of the leaf node and the
Other Issues
There are a few issues to consider when using this technique. The size
of the bitmap must be of a proper ratio. For instance, if the bitmap
were two bytes wide and 10000 bytes tall, dividing it up into quads
would cause a problem — there would be zero sized branches and
leaves. A good rule of thumb would be to not let the smaller size be
less than the log2 of the larger size.
There are ways to get around this. The usual method is to shrink
the original bitmap to a more manageable level and rebuild the
tree. Or, if the system isn’t memory-constrained, a better solution
might be to have three or more TQuadSurface objects, each one for
a different level of detail, and a broker object that understands how
to change the back rectangle needed into the proper operations in
the correct TQuadSurface.
Conclusion
By breaking up an image into smaller parts, the limitations of the
DirectDraw surface size can be overcome quickly. When reassembling
the image, special care must be taken to adequately render the proper
sections of several of the new smaller sub-images correctly. Rounding
errors can occur, and the use of ratios can result in a zoom capability.
By Ron Loewy
ExpressBars Suite 4
The New Look with Much Less Work
S ay you need to write a Windows application. You know, the old-fashioned kind.
Not a Web server, not a MIDAS server in the logic layer of an n-tier application,
nothing exotic and exciting like wireless services, just good old-fashioned windows,
menus, dialog boxes, and the like.
Ah! that’s easy you say. Let me show you how I them all, a Panel aligned to the correct corner of
create a new form here, drop a couple of controls, the window, BitBtns and SpeedButtons, and — if
add a menu component, throw in a panel, align you want to be fancy — an action list.
it to the top so it will be a toolbar, add a status
bar, and we’re ready to rock and roll. So far so good. And if you want to be even fancier,
Delphi now has docking support. So what’s stopping
A menu-bar component set. Who needs it? you from writing world-class Windows applications?
Delphi already ships with MainMenu and Pop-
upMenu components. There’s a StatusBar com- Then you open one of these fancy applications
ponent for — well — the status bar, ToolBar and the boys in Redmond keep writing, with the
CoolBar for the toolbars, or the granddaddy of cool gradient color bars on the left side of the
menus, or the Outlook SideBar with the groups
and the items, or the customizable toolbars and
menus that Office offers. Suddenly you realize
your application could be a lot cooler. Nothing
stops you from adding these features; you’re a star
programmer. Everything in Delphi is customiz-
able with tons of properties and events, etc. So
before you know it, two weeks have passed and
you wrote all this cool code for the colors, the
customization, the groups, and the docking, but
you missed your deadline for the real stuff the
application needed to do. Don’t you hate it when
that happens?
mentation. I used it for the minimal functionality in a new appli- Figure 3: Office XP-style menus.
cation I was writing, yet I find myself using it again and again.
With the release of ExpressBars Suite 4.0, I had an excuse to read We will return to the toolbar’s design capabilities in a moment, but
the “What’s New,” dive into the documentation and demonstra- for now let’s consider the Commands tab of the property editor.
tion applications, and appreciate how much more than a menu and Think of a command as a menu item or a button on a traditional
toolbar suite this product really is. The icing on the cake came toolbar. Now imagine that this command can be an edit box, a
when my QA engineer asked when we were going to convert an drop-down combo box, a static item, a color selection drop-down
older application to use the new menus and toolbars. box that can open a color selection box, a font selection drop-down
box, a spin box, a most-recently-used list, a lookup combo, a date
The Components editor with a drop-down calendar, a sub-menu, an image selection
ExpressBars installs 11 new components into the Component pal- drop-down box, a tree view component... You get the point.
ette on a new ExpressBars tab. Three of these components are used
for the SideBar: an Outlook-like component with groups and items If you can’t find the item you want to place on your toolbar among
that appear at the left side of an application’s window. TdxSideBar the different classes offered, you can create a control container
is the main Outlook bar-like component. Drop it on your form, command and assign an element of your design. Link this com-
double-click to activate the component editor, and start adding mand object to the object that you place on your form, with its
groups and items from there. For each group, you can define the visibility turned off, and you’re ready to go. One of the samples
visibility (groups might be activated based on user authorization that come with ExpressBars shows how to include a Delphi-like
for example), the size of the icons for the items it contains, its component palette on a toolbar.
position compared to that of other groups, and its name. For each
item, you can assign the icon, a tag, a hint, its position within the Every command item can be customized with an associated image,
group, and custom data. a title that can be displayed with it, and — for the different
command types — a set of properties and events that allow you
Once your groups and items are defined, it’s easy to use the to customize the use of this command. Since there are 21 built-in
OnItemClick event to change views based on the item selected. The command types, we won’t go into the details. Instead, let’s return
component allows you more fine-grained control to allow you to to the toolbars.
respond to events that happen when the active group changes, the
item selection changes, before or after a user edits an item, etc. Once you define a toolbar, you can define if it will be docked to
If you want to offer user customization of the SideBar, so they one of the four sides of the form. The bar manager will, of course,
can enable or disable groups, add items, etc., you’ll need to use allow you to customize to which side, if any, the toolbar can be
the TdxSideBarStore and assign the SideBar’s Store property to docked. You can also determine if users can let it float. Then you
point to it. Finally, the TdxSideBarPopupMenu can be assigned can start dragging and dropping command objects on it to define
to the SideBar component to provide “standard” customization its appearance. The commands will automatically be added and
operations popup menus such as Add Group, Remove Group, Rename aligned, so you won’t need to fight with the placement of the
Group, Remove Item, etc. elements the way you would if you were to use a simple TPanel
for a toolbar.
Now let’s move on to the real power of this product: the menu and
toolbar components. To start working on your menu and toolbar When a toolbar is selected in the component editor, you can assign
design, drop a TdxBarManager on your form and double-click on a plethora of options to it, such as the location where it is docked
it. Just as in Office, a toolbar and a menu are pretty much the and to which sides of the form it can’t be docked. You can also
same. Define toolbars in the component editor’s Toolbars tab. You define if the toolbar shows a size grip, if it takes the entire row
can designate a toolbar as the main menu of the form to have it (dock width), if it can span multiple lines, what its caption is when
automatically appear at the top of your form looking like a regular it floats, where it first appears when it is floating, what kind of
menu, or you can leave it as a regular toolbar that will appear as a border it has, and what kind of customization it provides.
docked toolbar or a floating command window. Customization is a big feature offered by this product.
S QL Tester 1.2 from Red Brook Software, Inc. lets you build and test SQL statements
without having to compile and run your application. This architecture lets you
use fewer query components, as well as store all your SQL statements outside your
application, so they can be changed without recompiling your program.
SQL Tester is particularly useful if you’re creating A pair of radio buttons on the SQL Tester toolbar
an application and you want to store your SQL let you choose whether you want to display BDE
statements in external files and execute them by or ADO databases. The Databases list box lets
using the LoadFromFile method of one of the you choose the database with which you want to
Delphi query components. Figure 1 shows the work. After you select the database, all the tables
main SQL Tester form. in that database are listed in the Tables list box.
Select a table and the fields for that table appear
Getting Started in the Fields list box.
SQL Tester lets you create SQL statements using
any database you can access via the BDE or ADO. To create a new query press I, or click
the Insert button on the navigator bar, then
type your SQL statement into the SQL Statement
memo control. Any time you need to insert a
table or field name into your SQL statement,
simply drag it from the table list or field list,
and drop it anywhere in the SQL Statement memo
control. Regardless of where you drop it, the
table or field name will always be placed at the
insertion point.
You can export the query result set in either ASCII or XML format. Another nice feature is that SQL Tester lets you save your current
You can also request a live result set when testing queries, so you SQL statement as a template if you need to create many similar
can update the result set to modify your test data without having queries. Click the Insert button on the navigator bar to create a query
to use another program. from the template, then click the Load from Template button to display
the list of templates you’ve created. Double-click the template you
If you’re working with a long SQL statement, the SQL Statement want to use and the SQL statement will be inserted into the SQL
memo control may not be large enough to allow you to see the entire Statement memo control at the insertion point. Templates don’t have
to be complete working SQL statements. For example, you might
save a list of field names you want to use in many queries.
Even if you don’t choose to load all your SQL statements from
files, SQL Tester can still be useful, because it can read SQL
statements from Delphi .dfm files (if the .dfm files were saved
as text). This feature allows you to extract the SQL statements
from the query components in your project and batch test them
in SQL Tester. After making corrections, simply write all the SQL
statements back to the .dfm files to update your application.
Conclusion
SQL Tester can cut your development time in two ways. First,
the ability to add table names and field names to SQL statements
using drag-and-drop can save a lot of typing when you’re creating
queries. Second, you can batch test queries when the database
schema changes. Batch testing all queries in an application using
SQL Tester is a lot faster than opening every query component
in your application in the Delphi IDE to see if the query still
executes properly with a new database structure. ∆
N o matter how intuitive you feel your application is, there will be times when its users, or at least some of them,
will be confused. They’ll need answers to questions that arise as to how to use the program, or guidance on
how to accomplish a particular task. It’s customary to provide a separate Help file for just such occasions. However,
sometimes that may seem like overkill, or you may feel it’s a waste of time to produce one as “nobody ever
reads the Help documentation anyway.” Moreover, it’s YAF (yet another file) that needs to be deployed with your
application, and another tool you have to learn. Or you can hire a contractor to create it.
Nevertheless, you should provide help in some form for users. You
have three choices:
Provide no help whatsoever. Bad idea.
Provide a full-fledged Windows Help file.
Provide a quick-and-dirty pseudo Help system.
If you’re serious about offering a quality software package, the first
option is obviously not a good one, so you must ask yourself:
Do I write a conventional Help file or not? If you do, you
can use a tool such as RoboHelp (http://www.ehelp.com), Doc-To-
Help (http://www.wextech.com), Microsoft Help Workshop, which
comes with Delphi (\Program Files\Borland\Delphi X\Help\Tools),
or HelpScribble (http://www.jgsoft.com/helpscr.html), which is writ-
ten in Delphi.
With a modicum of extra effort, you can provide “just enough” help
Figure 1: Nothin’ fancy, but it gets the job done. in a manner that’s not tied to the Windows OS (which is especially
S erious issues face us every day. How can I ensure my software is of the highest quality? What new technologies
should I learn this year? Will Delphi and/or Borland survive? Should I get involved with Linux/Kylix now or later?
Should I leave my company and become an independent consultant? Should I leave consulting and join a successful
company? This month we are going to take a break from such serious issues, and see how humor can help us keep
our cool in the face of incredible stupidity in the workplace.
As many of you, I subscribe to a number of Delphi Internet discus- Another, from a Delphi tool developer (maker of MyCoolTool)
sion lists and have learned much from reading their messages. offered valuable help and advice: “In a previous life I wrote
In the January 2000 issue of Delphi Informant I wrote a column intense WordPerfect macros (e.g. I created a popup calculator
entitled “The Case for Delphi” in which I shared information from accurate to four and a half decimal points, in a macro environ-
two discussion threads explaining Delphi’s strengths compared to ment that only supports whole numbers!), so I can objectively
those of Visual Basic. Here I will share a couple of recent discus- speak to WordPerfect’s abilities and limitations. If these folks are
sions from two of my favorite lists: The Delphi Advocacy Group still seriously considering this, set up a conference call and I’ll set
mailing list (tdag@yahoogroups.com) and the main Project JEDI them straight.”
list (Delphi-JEDI@yahoogroups.com). Discussions on TDAG are
sometimes technical in nature, but often concern themselves with Now I ask you: on how many lists can you expect this level of help?
the well-being of Delphi and the company that produces it. The This developer went on to provide specific details: “In addition to the
JEDI list typically discusses the various activities of that Project, obvious language limitations, there are these as well:
but also include some excellent discussions of translation issues from WordPerfect macros are noticeably slow. Probably 1000-10000
C/C++ to Delphi. The two threads I will present here, however, are times slower than Delphi programs.
unusual and non-technical, but — I think — quite entertaining. WordPerfect macros are extremely difficult to maintain. Expect
to pay 10-500 times as much time and money maintaining your
No more Delphi? No, we aren’t talking about the demise of our software.
favorite development tool, but rather about the precarious position of No MyCoolTool for WordPerfect.”
Delphi in some companies. While replacing Delphi with Visual Basic
in a particular shop isn’t farfetched, this short thread that appeared on Given the popularity of MyCoolTool among Delphi users, the next
TDAG does stretch the limits of credulity. Some well-known Delphi response was to be expected “No MyCoolTool for WordPerfect?
people participate on this list so I’ve changed the name of one well Well, what are you waiting for?” This market-savvy developer’s reply
known tool to MyCoolTool to protect the innocent. was “...waiting to see if the Kylix market will be bigger than the
WordPerfect macro tools market.” After this the thread faded into
The discussion started with this post: “Some of you will laugh, the cyber void.
thinking this is a joke. Some of you won’t believe me at all. At the
government agency where I work, in our ‘PC Team’ meeting last Delphi 6 to become open source? Discussions about Borland mar-
Friday, it was announced that we were now going to begin converting keting are much more common on TDAG than on the main JEDI
all of our Delphi programs into WordPerfect macros. The reason? list, but there are occasional exceptions, as this thread will demon-
Make things simple so anyone can understand them. All the lawyers strate. It appeared on the JEDI list on April 1 of this year.
and 25-year secretarial types figure they can write macros. So now the
big project is to eliminate EXEs altogether, and run the entire agency Here’s how it began: “Borland announced today there will be
under a (nearly obsolete) word processing program. The associate a significant shift in the way their products will be marketed.
‘programmer’ who works with me told them it was ‘a good idea.’ With the success of Kylix on the Linux platform there is no
There’s nothing like government work!” need anymore to sell Delphi 6, said Dale L. Fuller, President and
Chief Executive Officer of Borland, in an interview at NBC this
The first reply embodies the wonderful spirit of the developers on afternoon. Instead there will soon be the entire product portfolio
this list, who would rather fight than switch: “Just when you thought available for free. Beginning with Delphi 6 Borland plans to give
you heard it all. Stupidity has reached new levels. The ‘Associate’ out their major products for free one every other month in the
programmer you work with should be clubbed.” hope to take over the market leadership for software development.