0105
0105
ON THE COVER
5
On the Net
FEATURES
10
30
Sound+Vision
34
At Your Fingertips
REVIEWS
38
XTNDConnect RPM
Product Review by Warren Rachele
19
Distributed Delphi
41
25
2
43
44
Greater Delphi
DEPARTMENTS
Delphi Tools
Best Practices by Clay Shannon
File | New by Alan C. Moore, Ph.D.
Delphi
T O O L S
New Products
and Solutions
Book Picks
Learning WML & WMLScript
Martin Frost
OReilly & Associates
AutomatedQA
Price: US$349 for a single license; fiveuser license, US$1,399; 10-user license,
US$2,499.
Contact: (702) 262-0609
Web Site: http://www.automatedqa.com/
products/aqtest.asp
ISBN: 1-56592-947-0
Cover Price: US$34.95
(179 pages)
ISBN: 0-471-39951-5
Cover Price: US$44.99
(413 pages, CD-ROM)
Delphi
T O O L S
New Products
and Solutions
Book Picks
Writing Stored Procedures for
Microsoft SQL Server
Matthew Shepker
SAMS
ISBN: 0-672-31886-5
Cover Price: US$39.99
(346 pages)
ISBN: 0-201-70936-8
Cover Price: US$39.95
(408 pages)
Delphi
T O O L S
New Products
and Solutions
Book Picks
Special Edition Using XHTML
Molly E. Holzschlag
QUE
CD-ROM is available for purchase from the AbriaSoft corporate Web site. It is also included
in the Abria SQL Suite.
AbriaSoft also offers MySQL
instructor-led courses for corporations or groups. Future
MySQL education products will
include an additional computer-
ISBN: 0-201-71103-6
Cover Price: US$39.95
(441 pages, CD-ROM)
ModelMaker announced
the release of ModelMaker
Code Explorer Delphi 5. Code
Explorer is a substitute for
On the Net
Web Applications / ASP / ISAPI / CGI / PHP / WebBroker / Delphi 5
By Dr Mark Brittingham
Web Applications
The term Web applications is used to denote any
application written to program a remote browser.
By definition, this leaves out a number of technologies spawned by the Internet. For example, peer-topeer technologies like Napster and Gnutella owe
their creation to the ubiquity of the Internet, but
theyre still fairly traditional networking applications. Ill have more to say about Napster and
the social opportunities created by the Internet
in the final section of this article. For now, however, its important to focus on browser-based Web
5 May 2001 Delphi Informant Magazine
Server-side Technologies
In contrast to the disappointments of client-side
technologies, weve witnessed an explosion of
server-side technologies. All of these technologies
are based on the idea that a document sent to the
browser can be dynamically created by software,
rather than simply being read from a file.
On the Net
To reiterate, your job as a server-side developer is to deliver HTML
and JavaScript to the browser that makes it act the way you want
it to. Thats it; theres nothing mystical about it. You can get as
obscure and complicated as you want on the server side, and it
doesnt amount to a ham-hock at a vegetarian soire unless it delivers
HTML/JavaScript to the browser. This also means that you should
abandon any idea that you can succeed by just sticking with Delphi
or any other server-side technology. To be effective in this domain,
you must spend time mastering HTML and JavaScript, or your Web
applications will always be ugly or will perform badly. Of course,
since most Web applications are ugly and perform badly, you might
find that no one notices. But Ive got to believe you arent like that, or
Id lose all motivation to finish this article.
ND-IntraWeb
Just to demonstrate that I have a sense of humor, Ill kick off
this discussion of Web application technologies with the only one
that, indeed, does not require that you learn HTML and JavaScript.
ND-IntraWeb (from Nevrona Designs, http://www.nevrona.com/
intraweb) is a Delphi-specific solution that permits you to create and
use a Delphi application as a kind of Web server by hiding it
behind the ND-IntraWeb server. That is, people visiting your site will
see HTML pages that ND-IntraWeb creates based on your Delphi
forms. So, if they click on a button in the browser, ND-IntraWeb
will click the button on your application and deliver the resulting
form to the user.
The strength of ND-IntraWeb is that it permits you to leverage the
RAD nature of Delphi development in a Web application environment. By taking control of the production of your HTML and
JavaScript, ND-IntraWeb frees you to create applications with the
more directly visual Delphi RAD tools.
ND-IntraWeb has two weaknesses: the user interface presented to
your user, and the scalability of an ND-IntraWeb application. An
ND-IntraWeb application will look more like a Windows application
than the typically-more-creative and graphical layout of HTML Web
pages. While you can embed your application output in an HTML
page, you will still need to remain conscious of the differences in
the presentation of your work, if you dont want your application to
depart too jarringly from the HTML idiom.
ND-IntraWebs scalability is limited by the fact that it runs a new
copy of your application for each visitor to your site in order to
maintain a consistent state. Normal Web applications dont maintain
a great deal of information in memory for each user. Thus, it is
quite possible to serve many thousands of simultaneous users of your
system. In contrast, ND-IntraWeb is limited to 50-250 simultaneous
users, depending on the size of your application.
If your application is targeted at a small set of users (e.g. a sales
force automation application for a small- to medium-sized business)
and you have a rapidly-approaching deadline, you may well find NDIntraWeb to be an ideal solution.
You use the ASP scripting language to add dynamic content to the
pages in your site. While ASP scripting is reasonably robust, this
technologys real power comes from its tight integration with COM.
The best ASP strategy is generally to implement the visual aspects of
your site with HTML, and to use COM components written in Delphi
to supply the dynamic content. Even Microsoft recommends that you
restrict your VBScript to simple tags and implement your program
logic in your COM objects. If youre committed to ASP, taking this
approach will also help you move up to ASP+ once its available.
Another big strength of ASP is its sheer popularity. There are many
helpful ASP sites on the Web and plenty of good books. Despite
Microsofts recommendation that you emphasize COM objects in
your ASP development, this technologys popularity is primarily due
to the fact that ASP permits you to write your code on the same
page as your HTML. Because both visual layout and code are all in
one place (except for any COM objects you have created), an entire
project can be built one page at a time.
The downsides to ASP are the speed of an interpreted script, and the fact
that you are likely to be working in Basic; the other scripting languages
are rarely used, and nearly all of the documentation is for VBScript.
And dont buy into any arguments over whether ASP is faster or
slower than the ISAPI technology (covered later in this article). The
ASP DLL is an ISAPI DLL, and so cannot, by definition, be faster.
Macromedia UltraDev
UltraDev isnt a development technology, but rather a tool for helping you build ASP pages (and others) by supporting the scripting
process. I cover it briefly because Ive met a number of people who
think that its indeed a new development technology, above and
beyond the technologies described here.
UltraDev (from Macromedia, http://www.macromedia.com/
software/ultradev) is more sophisticated than other Web development
environments because it permits you to work with live data sets. That
is, you dont just see a blank spot where the output of your dynamic
script would appear: the tool helps you make database-to-output links
so you actually see what the user would see (or close to it). As Internet
development tools go, this is a bit of a breakthrough and is why
UltraDev generated a lot of excitement when it was released.
Personally, Im only mildly excited about this. Since Web output
of database data is already fairly simple, there is less of a win here
than there was back when VB and Delphi brought form-based design
and RAD development to Windows. Nonetheless, its something to
consider especially if youre mostly a visual page designer rather
than an HTML hacker like me.
ColdFusion
ColdFusion (from Allaire Corp., http://www.coldfusion.com/Products/
coldfusion) is like ASP in that it is a scripting language implemented
by the ColdFusion Application Server, an application that interacts
with a Web server to interpret HTML files with embedded scripts. On
Windows NT, ColdFusion usually communicates with the Web server
via an ISAPI.DLL. ColdFusion is also available on various flavors of
UNIX. ColdFusion is best for small- to medium-sized projects because
the interpreter is relatively slow. You can scale ColdFusion, but you do
so only at great expense: multiple servers bound together with Cluster
Cats or a similar technology. Indeed, the ColdFusion people have gone
to such extraordinary lengths to convince the world that the technology
is scalable, that youre almost guaranteed it is its greatest weakness.
On the Net
ColdFusions greatest advantage is that its scripting language is quite
easy to learn. If youre coming from the HTML design world, and are
looking for an HTML-like, easy-to-learn language, ColdFusion would
be the tool to try. If this isnt your background, then Id suggest you give
it a pass. Its too expensive and too slow to be worth considering, even if
the language is somewhat easier to learn than ASP.
PHP
PHP is an open-source scripting language based on the C programming language syntax. PHPs main advantages are its power (especially in pattern matching) and its price (free). Its also available on a
large number of platforms.
The drawback to PHP is related to the benefit of its power: its underlying
scripting language is more complex than any of the other scripting
languages. Indeed, PHP scripting isnt any easier than the ISAPI development discussed below, and it doesnt have the advantages of ISAPIs
speed, or of Delphis advanced IDE and debugger. Nor does it have the
seamless integration with COM found in ASP (although you can spawn
external programs and retrieve the results). In addition, function calls
are relatively expensive in PHP, so any reasonably complex application
will either be implemented in one very long function, or youll incur a
significant performance penalty compared to the alternatives.
This call requests that the Web server spawn the CGIApp.exe
program and capture its output (on the console) for output to
the browser. WinCGI applications operate in essentially the same
manner, except the input/output are performed via the Windows
registry rather than via the console.
The advantage of CGI applications is that they are relatively simple
to create, support a broad array of development languages (Perl being
perhaps the most popular), and are available on any platform. The
biggest drawback to CGI applications is that every Web page request
results in the loading and initialization of a full executable. Thus,
CGI applications are, in general, the worst performing dynamic Web
technology available. Nevertheless, for small sites, CGI may well be
the fastest way to develop and deploy a dynamic application.
ISAPI
If you conclude that all of the technologies described so far suffer
from performance limitations, youre not far from the mark. While
ASP can be made considerably faster by using COM objects, theres
no technology quite so fast as working directly with the requests
coming from the users within the process boundary of the Web
server. This is exactly what ISAPI does. ISAPI is implemented as a
DLL thats used by the Web server, either when requested in the URL
(like CGI), or when a specific file extension is requested.
Things get interesting with ISAPI and a bit more difficult. As
previously mentioned, the Web is stateless; every page request is
a thing unto itself. Thats why ASP and ColdFusion (and others)
implement what is called state management to help you keep data
entered or requested by a user on one page in synch with information
presented or requested to that same user on other pages.
7 May 2001 Delphi Informant Magazine
The bad news is that ISAPI itself does not implement state management for you. The good news is that Delphis ISAPI framework,
WebBroker, implements state management in Delphi 6, and can
easily be extended to do so in Delphi 5 (see Real-world Web Apps
in the July 2000 Delphi Informant).
When you code ISAPI, you build your site in two parts: visual mode
(HTML) and code mode (coding the ISAPI DLL). You or your team
will create the HTML pages for your site with an HTML tool like
HomeSite (http://www.allaire.com/products/homesite/40) or DreamWeaver (http://www.macromedia.com/software/dreamweaver). These
HTML files will use tags wherever dynamic information will
appear. The Web browser doesnt request the HTML files, but instead
requests output directly from the ISAPI DLL. The DLL will retrieve
the files and replace any tags with the appropriate dynamic content.
HTML files handled in this way are often referred to as templates
a term discussed with Java and JSP later.
As you can see, the visual design of ISAPI Web applications is quite
separate from the logical design. Overall, the programming model
is extremely simple: get request and generate tag replacements. However, I strongly suggest you get a copy of the MDWeb libraries featured in my Real-world Web Apps article if youre using Delphi 5.
The library can be downloaded free from http://www.xapxone.com.
ISAPIs primary benefit is that it has the fastest page production
time of any Web application technology. If youre implementing a
medium-sized Web application (approximately 100,000 to 1,000,000
hits per day), you may well be able to provide this in a single ISAPI
DLL running on a single server, depending on the complexity of
your application.
As with all of the technologies discussed so far, you can scale an
ISAPI application easily enough with round-robin, or other router- or
switch-based request distribution methods.
By the way, if you choose ISAPI, download the Omni Web server
for development purposes. It integrates easily and nicely with Delphi
to make it trivially easy to test and debug your DLLs. If you dont do
this, but try to use PWS or IIS for debugging, youre likely to think
youve died and gone to Coding Hell. D6 promises a debugging environment that should eliminate the need for any external Web server,
but until then Omni is a great way to go: http://www.omnicron.ca.
If ISAPI is the fastest of all dynamic page delivery methods, and
its fairly easy to implement, you might be wondering why ISAPI
applications arent more common. One reason is surely that ISAPI
applications arent easy to create unless you have Delphi
and the WebBroker libraries. WebBroker, like the rest of the
VCL, makes a fairly complex technology far simpler than it
would otherwise be. Unfortunately, Delphi doesnt have a large mindshare in the Web development community, so ISAPI retains its
reputation as a difficult and time-consuming method for developing
Web applications.
The second reason ISAPI applications arent more common is that
even dynamic sites are themselves becoming dynamic. That is, personalization and content management technologies are being widely
deployed in large Web sites. These technologies work with dynamic
applications to manage real-time content customization and the
deployment of content by non-Web professionals. Its often not realistic to deploy an ISAPI-based solution in a large Web site. This would
preclude integration with the available personalization and content
On the Net
management packages which generally support only Java and ASP.
Of course, the vast majority of Web sites arent large enough (or rich
enough) to require such technology. For these Web sites, Delphi and
ISAPI are very well suited.
One last point: It isnt true, as some have said, that the smallest
error in an ISAPI DLL will crash the server. If youre working in
Delphi/WebBroker, your user will just get an error message.
If youre already familiar with Delphi, and youre willing to think in
terms of programming the browser, you should find Delphi ISAPI
development easy to master.
WebHub
Another technology often mentioned in connection with Delphi is
WebHub (from HREF Tools, http://www.webhub.com). WebHub
does one thing that I truly envy. It gives you essentially all of
the benefits of ISAPI, while letting you create EXEs instead. The
benefit of this is that you dont have to shut down your Web server,
or unload your DLL, every time you want to change your app.
Thus, your state information is preserved, and you can just debug
your executable.
WebHub has a fairly sophisticated request distribution mechanism, a
scripting language, and full session management. In some ways, you
can think of WebHub as trying to provide the enterprise environment
for Delphi that J2EE provides for Java (discussed next).
The biggest drawback to WebHub is that it adds complexity to
Delphi Web development without necessarily providing any advantage in scalability. That is, while there are a variety of software scalability tools in the product and the architecture is quite sophisticated,
my sense is that anyone looking for this kind of scalability would be
more likely to work with Java technologies. Java too has sophisticated
tools for distributing functionality among multiple servers, but it also
boasts a broad range of third-party tools for personalization, content
management, deployment, and performance monitoring.
The tags in Java Server Pages can be used to call Java objects (including Enterprise Java Beans) on the server to provide dynamic HTML
in much the same manner as ASP can call COM objects. The
advantage of this approach is that this makes it possible to build a
sophisticated environment of interacting Java objects natively built
and third-party and then plug the Web pages into this environment. Combined with J2EE-compliant application servers like those
from BEA, IBM, or Borland, this makes for a very powerful, scalable,
distributed technology.
While its certainly possible to build small- to medium-sized Web
applications in Java, I think its relevant for Delphi developers to ask
whether the transition to Java is worth the investment for such sites.
Delphi/WebBroker-based applications are faster and much easier to
deploy compared to JSP (at least on Windows-based servers), so there
are situations where a Delphi-based solution is clearly preferable.
Perhaps the most relevant question to ask when deciding between
Delphi/WebBroker and a JSP solution is whether your application
and/or site will require the use of third-party content management,
personalization, or other technologies that would be difficult to integrate with an ISAPI DLL.
Microsofts .NET
Microsoft has recently pulled out all the stops in promoting its new
.NET framework. Were told that .NET represents the future of Internet and Web development. With .NET, Microsoft has indeed identified the key fulcrum point for future technological developments:
loosely coupled, network aware, component software. However, this
isnt Microsofts vision its Suns vision in creating its Java 2 Enterprise Edition (J2EE) specification. Of course, when it first struck me
that Microsoft might be building a derivative technology, I was, frankly,
stunned with disbelief. After recovering, however, I began to look for
indications of how this incipient battle would play out.
On the Net
At this point, I would suggest that .NETs success depends in part on
developments in handheld computing and Web appliances, application server technology, peer-to-peer technology, and Linux.
Handheld and appliance devices will be a vital part of future
distributed client computing and, having developed for the Palm,
Im certain that the PocketPC is poised to make significant inroads
in this area. Conversely, Suns handling of Java 2 Micro Edition
(J2ME) doesnt exactly inspire confidence. The Palm OS continues
to hold a majority market share, and theres little chance that
Microsoft will displace embedded Linux for the great majority
of Internet appliances. Thus, the future of small-footprint Internet-enabled clients will be browser-based technologies like WML,
rather than embedded Java or .NET applications. The reason is
simple: If no vendor wins a market majority in this space, the
only way to support them all is to support platform-independent
standards like HTTP, XML, and WML.
Web-based application servers will increasingly adopt the peer-topeer model within their processing boundaries. That is, under the
guise of Web services I predict a near explosion of Java beans and
.NET components for use in building large server systems. There
will be a strong incentive for organizations to standardize on one
or the other approach in order to maximize the interoperability
of their Web services components. If .NET is seriously delayed or
buggy, Java may well dominate the enterprise market to such an
extent that .NET is forever relegated to niche status. Currently, Java
does not enjoy this dominance and one should never underestimate
the capacity of UNIX/Linux vendors to shoot themselves in the
foot. At this point, the final outcome of the battle between .NET
and Java in the enterprise is highly uncertain, even though .NET is
merely a beta technology.
This implication of the shift to Web services in the enterprise is that
nearly all enterprise systems will become at least partially distributed.
Thus, rather than implementing dozens of disparate systems, enterprises will increasingly view their internal and external systems as a
Web of interacting subsystems. Within this Web, customer-oriented
applications will be almost exclusively Web applications (browserbased). Web applications will continue to grow in popularity for
internally deployed software. However, I also believe that well see a
resurgent interest in client and peer-to-peer applications built using
either Java or .NET. These desktops, often communicating with
servers and each other via XML, will define the next generation of
enterprise software.
Perhaps the smartest thing that Borland is doing for Delphi in this
developing arena is to support the use of EJBs in future editions of
Delphi. By linking Delphi into this model of computing, Borland
helps assure that Delphi will remain relevant in a Java-centric world.
Indeed, Delphi may well become the tool of choice for building fast,
compiled client applications that need to interact with Web services
delivered by enterprise application servers.
In Closing
At this juncture, I cannot help but be excited by the future of
Internet application development. We are entering an era where
ubiquitous, standards-based computing will permit us to leap beyond
the construction of technological foundations, into the realm of
creative social computing. Even if youre making copies of your
resume on the back of now-worthless stock options, rest assured the
best and most creative ideas in computing still lie ahead.
By Bill Todd
he Service and Service Application wizards in Delphi make writing Windows NT/2000
services easy. However, Delphi doesnt provide any tools to help you install and
control services. This article examines the Windows API functions that let you install,
remove, control, and configure services. It also presents a simple service manager
component (its class is named TdgServiceManager) you can use in your applications to
manage Windows services.
Once you have written a Windows service, you
need to install it before you can test it. Delphi
makes this easy if you want to install or remove
a service manually. All you have to do is run the
application from the command prompt with the
/Install command line switch. You can remove the
service by using the /Uninstall switch.
Adding a Service
procedure TdgServiceManager.ReleaseServiceHandle;
begin
if FServiceHandle <> 0 then begin
if not CloseServiceHandle(FServiceHandle) then
RaiseLastWin32Error;
FServiceHandle := 0;
if not CloseServiceHandle(FSCMHandle) then
RaiseLastWin32Error;
FSCMHandle := 0;
end;
end;
Deleting a Service
Deleting a service is much easier than adding one. The DeleteTheService
method shown in Figure 3 first gets a handle to the service by calling
the GetServiceHandle method, shown in Figure 4. It passes the handle
as the parameter to DeleteService, which flags the service for deletion.
The Service Control Manager will delete the service when it isnt
running, and when all handles to the service have been closed.
Since the service wont be deleted until all of the handles have been
closed, the method finishes by calling ReleaseServiceHandles. Note that
calling DeleteTheService will not delete the service immediately if it is running. If you want to delete the service immediately, call StopTheService
(described later in this article), then call DeleteTheService. You can do
this the other way around by calling DeleteTheService first and calling
StopTheService later. If you do this, however, call ReleaseServiceHandle
after calling StopTheService, because StopTheService has to open a handle
to the service in order to stop it.
With the exception of AddTheService, all of the methods that manipulate the service begin by calling GetServiceHandle. GetServiceHandle
exits if FServiceHandle is not 0, since a non-zero value means that the
handle is already open. This test works because the ReleaseServiceHandle
method sets FSCMHandle and FServiceHandle to zero after closing the
handles. If FServiceHandle is 0, a call to OpenSCManager gets a handle
to the ServiceControlManager. This handle is used as the first parameter
in the call to OpenService, which returns the handle to the service. The
second parameter passed to OpenService is the ServiceName property,
and the third is the constant that indicates what type of access you
want to the service.
procedure TdgServiceManager.GetServiceHandle(
ServiceName: string);
begin
if FServiceHandle <> 0 then
Exit;
FSCMHandle := OpenSCManager(PChar(FMachineName), nil,
SC_MANAGER_ALL_ACCESS);
if FSCMHandle = 0 then
RaiseLastWin32Error
else begin
FServiceHandle := OpenService(FSCMHandle,
PChar(ServiceName), SERVICE_ALL_ACCESS);
if FServiceHandle = 0 then
RaiseLastWin32Error
end; // if
end;
function TdgServiceManager.WaitForServiceStatus(
ServiceStatus: DWORD): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to FWaitTime do
if GetServiceStatus = ServiceStatus then
begin
Result := True;
Break;
end
else
Sleep(100);
end;
status record showing the current status of the service when the
call returns. Finally, StopTheService calls the WaitForServiceStatus
method (see Figure 8), passing the SERVICE_STOPPED constant
to prevent the method from exiting until the service has actually
stopped.
Before going any farther, lets look at the GetServiceStatus and
WaitForServiceStatus methods in detail. GetServiceStatus, shown in
Figure 7, calls the Windows API QueryServiceStatus function, passing
the service handle as the first parameter and a variable, ServiceStatus,
of type _SERVICE_STATUS as the second. After the call, the
ServiceStatus.dwCurrentState field contains the current status of the
service and is assigned to Result. The constants that correspond to
the various states that the service can be in are listed in the Win
SDK help file under SERVICE_STATUS, as are the other fields in
the ServiceStatus record.
If youve ever started or stopped a Windows service manually, you
know it doesnt happen instantly; typically it takes a couple of seconds. To prevent users of the service manager component from
doing something that depends on a change in the state of a service
before the service actually changes, state all of the methods that
change the state of the service, such as StartTheService, then call
WaitForServiceStatus to prevent the method from returning until the
service has actually changed state.
WaitForServiceStatus, shown in Figure 8, takes the status constant to
wait for as a parameter and calls GetServiceStatus every 100 milliseconds
in a loop until the desired status is returned. The WaitTime property
of the service manager component controls the maximum time the
WaitForServiceStatus waits to ensure that it cannot loop indefinitely. If
WaitForServiceStatus times out, it returns False; otherwise it returns True.
13 May 2001 Delphi Informant Magazine
procedure TdgServiceManager.GetServiceConfiguration(
var StartType: DWORD; var Path, ServiceStartName,
DisplayName: string);
begin
{ Get the service configuration values. }
GetServiceConfig;
try
StartType := PCfg^.dwStartType;
{ Convert the strings from PChar to ANSI strings. }
Path := StrPas(PCfg^.lpBinaryPathName);
ServiceStartName := StrPas(PCfg^.lpServiceStartName);
DisplayName := StrPas(PCfg^.lpDisplayName);
finally
{ Free memory allocated by call to GetServiceConfig. }
FreeMem(PCfg);
end; // try
end;
procedure TdgServiceManager.GetServiceConfig;
{ Allocates a buffer and loads the service configuration
information into it. Note that any method that calls
this method is responsible for calling FreeMem(PCfg) to
free the memory allocated by the call to AllocMem. }
var
NumBytes: DWORD;
begin
{ Get a handle to the service. If no handle is
returned, then exit. }
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then
Exit
else begin
{ Call QueryServiceConfig to find out how much memory
is required to hold the configuration information.
The number of bytes required is returned in the last
parameter, NumBytes. }
QueryServiceConfig(FServiceHandle, nil, 0, NumBytes);
{ Allocate the required memory. }
PCfg := AllocMem(NumBytes);
{ Call QueryServiceConfig again passing memory buffer
and its size to get configuration information. }
if not QueryServiceConfig(FServiceHandle, PCfg,
NumBytes, NumBytes) then
RaiseLastWin32Error;
end;
end;
Conclusion
If you need to manipulate Windows services in code, whether or not
theyre services you have written, the Windows API provides all of the
functions you need. The TdgServiceManager component described
in this article provides a simple wrapper around these functions to
make using them easier for Delphi programmers. For more detailed
information on the Windows API functions, look up the function in
the Windows SDK help file that comes with Delphi.
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2001\MAY\
DI200105BT.
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 programming books and over 80 articles, and is a member of Team Borland, providing
technical support on the Borland Internet newsgroups. He is a frequent speaker
at Borland Developer Conferences in the US and Europe. Bill is also a nationally
known trainer and has taught Delphi programming classes across the country and
overseas. Bill can be reached at [email protected].
else
PasswordStr := PChar(Password);
if DisplayName = '' then
DisplayNameStr := nil
else
DisplayNameStr := PChar(DisplayName);
SCLock := LockServiceDatabase(FSCMHandle);
if SCLock = nil then
RaiseLastWin32Error;
try
{ Change the configuration. }
if not ChangeServiceConfig(FServiceHandle,
SERVICE_NO_CHANGE, StartType, SERVICE_NO_CHANGE,
PathStr, nil, nil, nil, StartNameStr,
PasswordStr, DisplayNameStr) then
RaiseLastWin32Error;
finally
if not UnlockServiceDatabase(SCLock) then
RaiseLastWin32Error;
end; // try
end; // if
end;
Result := 0;
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then Exit;
if not QueryServiceStatus(FServiceHandle, ServiceStatus) then
RaiseLastWin32Error;
Result := ServiceStatus.dwCurrentState;
end;
function TdgServiceManager.WaitForServiceStatus(
ServiceStatus: DWORD): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to FWaitTime do begin
if GetServiceStatus = ServiceStatus then
begin
Result := True;
Break;
end
else
Sleep(100);
end; // for
end;
procedure TdgServiceManager.StartTheService;
var
PArgs: PChar;
begin
PArgs := nil;
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then Exit;
if not StartService(FServiceHandle, 0, PArgs) then
RaiseLastWin32Error;
end;
procedure TdgServiceManager.StopTheService;
var
ServiceStatus: _SERVICE_STATUS;
begin
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then
Exit
else begin
if GetServiceStatus <> SERVICE_STOPPED then
begin
if not ControlService(FServiceHandle,
SERVICE_CONTROL_STOP, ServiceStatus) then
RaiseLastWin32Error
else begin
{ Wait for the service to stop. }
if not WaitForServiceStatus(SERVICE_STOPPED) then
raise EServiceStatusWaitError.Create(
'Service did not stop.');
end; // if
end; // if
end; // if
end;
procedure TdgServiceManager.PauseTheService;
var
ServiceStatus: _SERVICE_STATUS;
begin
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then
Exit
else
if GetServiceStatus = SERVICE_RUNNING then
if not ControlService(FServiceHandle,
SERVICE_CONTROL_PAUSE, ServiceStatus) then
RaiseLastWin32Error
else
{ Wait for the service to pause. }
if not WaitForServiceStatus(SERVICE_PAUSED) then
raise EServiceStatusWaitError.Create(
'Service did not pause.');
GetServiceConfig;
try
StartType := PCfg^.dwStartType;
{ Convert the strings from PChar to ANSI strings. }
Path := StrPas(PCfg^.lpBinaryPathName);
ServiceStartName := StrPas(PCfg^.lpServiceStartName);
DisplayName := StrPas(PCfg^.lpDisplayName);
finally
{ Free memory allocated by call to GetServiceConfig. }
FreeMem(PCfg);
end; // try
end;
procedure TdgServiceManager.SetServiceConfiguration(
StartType: DWORD; Path, ServiceStartName, Password,
DisplayName: string);
var
PathStr:
PChar;
StartNameStr:
PChar;
PasswordStr:
PChar;
DisplayNameStr: PChar;
SCLock:
SC_LOCK;
begin
{ Get a handle to the service. If the handle could not be
obtained then exit. }
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then
Exit
else begin
{ For each string parameter, see if the value is a null
string. If so, assume user doesn't want to change
that value and set the parameter to nil. }
if Path = '' then
PathStr := nil
else
PathStr := PChar(Path);
if ServiceStartName = '' then
StartNameStr := nil
else
StartNameStr := PChar(ServiceStartName);
if Password = '' then
PasswordStr := nil
else
PasswordStr := PChar(Password);
if DisplayName = '' then
DisplayNameStr := nil
else
DisplayNameStr := PChar(DisplayName);
SCLock := LockServiceDatabase(FSCMHandle);
if SCLock = nil then
RaiseLastWin32Error;
try
{ Change the configuration. }
if not ChangeServiceConfig(FServiceHandle,
SERVICE_NO_CHANGE, StartType, SERVICE_NO_CHANGE,
PathStr, nil, nil, nil, StartNameStr,
PasswordStr, DisplayNameStr) then
RaiseLastWin32Error;
finally
if not UnlockServiceDatabase(SCLock) then
RaiseLastWin32Error;
end; // try
end; // if
end;
procedure TdgServiceManager.DeleteTheService;
begin
{ Get a handle to the service. If the handle could not
be obtained then exit. }
GetServiceHandle(FServiceName);
if FServiceHandle = 0 then
Exit
else begin
if not DeleteService(FServiceHandle) then
RaiseLastWin32Error;
Distributed Delphi
MTS / COM+ / ADO / Microsoft Access / Windows NT 4 / Windows 2000 / Delphi 4, 5
By Malcolm Matthews
Creating MTS/COM+
Components
Error Handling, Debugging, Installation, and More
his article examines how to write MTS components for Windows NT 4, and the new
COM+ components for Windows 2000. It begins with an explanation of MTS and the
theory behind it. Then the creation of MTS components is described, including tips on how
to reduce the number of problems you can encounter.
Topics covered include error handling, debugging, security, and installation of the components. This article assumes a basic understanding
of COM and the creation of COM components
with Delphi. The concepts are demonstrated
with a simple database component that uses
ADO to access an Access database.
What Is MTS?
Microsoft Transaction Server is far more than just a
transaction server. Its a COM component hosting
environment that:
manages system resources, e.g. processes,
threads, and connections;
manages server object creation, execution, and
deletion;
automatically initiates and controls transactions;
Workstation
MTS Server
Laptop computer
The key benefit of MTS is that it frees developers from many of these aspects of middle-tier
application development, allowing them to
concentrate on the business logic theyre
implementing.
Computer
Database Server
Distributed Delphi
Whats a Component?
Components arent objects resulting from object-oriented analyses. A typical object has methods and properties. Consider a
customer object thats installed in the middle tier. If we want
to find the customers name, address, and telephone number, we
need to call a method to instantiate it with the data for the
required customer, retrieve the name property, then the address
property, and finally the telephone number. Each of these requires
a network round trip. That adds up to four return trips, which
takes too much time and is inefficient.
In practice, a single method call that returns all of the data is
used. While this may result in unnecessary data being fetched, the
performance benefits far outweigh the possible waste.
Using a single method call has another benefit upon which MTS
capitalizes. In the usual object model, the object must store the
data, ready to supply more when required. This means it can only
interact easily with one client. Returning all of the data at one
time means it doesnt need to retain any information; its stateless
and therefore free to act for another client. This is at the heart of
MTS. Thus business objects become glorified code libraries and
are usually referred to as components.
Resource Management
MTS usually destroys a component when it has finished a method
call for a client. This sounds crazy, and it would be if the components had to be completely re-created each time, including
all their resources (such as threads and database connections)
especially database connections as theyre very expensive to
establish. To overcome this, MTS pools many of its resources. So
when a new component requires a database connection, its given
an existing one. When its finished with it, MTS takes it back
instead of closing it. The threads used to run the components are
also pooled in this way.
Consequently, the overhead of repeatedly creating and destroying
components is reduced, and when the number of client connections is high enough, is fully compensated by the reduced
resources. The load on the database will also be reduced.
Understanding the way MTS creates and frees components, and
pools the supporting resources, is key to creating effective MTS
components. Note: For database connection pooling to work, the
database driver must support it.
MTS
Client Application
Context Object
MTS Component
Figure 3 (Top): The ActiveX page of the New Items dialog box.
Figure 4 (Bottom): Launching the MTS Object wizard.
COM+
MTS runs on Microsoft Windows NT 4. For Windows 2000, Microsoft has merged it into the COM infrastructure itself to create
COM+. This contains all of the features of MTS, plus a few more. So
what was previously an MTS package becomes a COM+ application.
As no changes are required for MTS components to run on Windows
2000, lets consider them the same thing.
One of the new features in COM+ is that components as well
as database connections can be pooled; this leads to significant
performance increases. MTS components are dynamically created
and destroyed each time theyre required for Just-in-Time Activation. Any resources they use are pooled and shared between the
components as they run. Before we look at creating MTS components using Delphi, there is one aspect of the details of the way
that this works to understand.
When a client application creates a COM object, it receives a reference to that object, or more correctly an interface to that
object. If the object is destroyed, this reference becomes invalid and
subsequent method calls will fail. MTS needs to do something to
avoid this. It also needs to maintain some state information for the
client application, such as security and transaction information.
Distributed Delphi
Method
DisableCommit
EnableCommit
IsCallerInRole
IsInTransaction
IsSecurityEnabled
SetAbort
SetComplete
Description
Used for stateful components;
beyond articles scope
Used for stateful components;
beyond articles scope
Used for security checking within
components
Used to check if MTS has created a
transaction; transaction model can be
changed after installation
Used to check if security checking has been
activated; it is also configurable after
installation
Tells MTS the component has finished and
wants the transaction aborted
Tells MTS the component has finished and
wants the transaction committed
This is where the context object comes in. Although not strictly
accurate, I think of this as pretending to be the MTS component, so
the clients reference is always valid (as illustrated in Figure 2).
When the client first creates an MTS component, the context object
is created. It exists while the client holds a reference to the component. When a method is requested, the context object creates the
component, runs the method, and then destroys the component. The
exact operation of this object and MTS is more complicated, but this
simplification helps and is perfectly adequate.
It may appear that having a context object for each client violates the
idea of reducing resources by Just-in-Time Activation. However, the
context objects are very small, and therefore impose far less load on
the MTS server than the actual MTS components.
Distributed Delphi
function TMTSDemo1.GetData: OleVariant;
begin
try
// Main processing goes here.
SetComplete;
except
SetAbort;
Raise;
end;
end;
For this example, well create a single method named GetData that
returns a variant (see Figure 6). This will actually be an ADO
Recordset. I could include the ADO type library on the Uses tab of
the Type Library Editor, and specify the return type as Recordset.
However, a variant is simpler for this example.
(Note: Delphi 4 had problems returning exceptions to the client
application when the return type was Recordset. It blew up the
MTS component! I dont recommend using Delphi 4 for MTS
components anyway; the MTS COM components it produces are
prone to resource leaks.)
Writing Methods
Having created the method skeleton using the Type Library Editor,
we now need to write the code. The most important thing is
to tell MTS when weve finished, so it knows it can destroy the
component (or put it back into the pool). If there was a transaction,
we also need to tell MTS if we want the transaction to be committed or rolled back. If we dont, MTS will treat the component as
stateful, and we wont gain the resource savings. Figure 7 provides
more information on context object methods.
We need to use the last two methods, SetAbort and SetComplete,
here. Its a good idea to use the skeleton in Figure 8 for each method
in an MTS component thats exposed through the COM interface.
If anything goes wrong, MTS is told, and an exception is raised and
marshaled across the COM interface to the client.
SetAbort and SetComplete act as votes in the outcome of the transaction. More than one component can participate in the transaction, as we shall see shortly. As soon as one of them calls SetAbort,
the transaction is rolled back. This includes work by all components in the transaction. No more database activity is allowed after
this, so its important that SetAbort is only called when needed,
and that any exception handling doesnt allow further database
activity. If the component does try to access the database, a helpful
$8004E004 exception will be raised. This is defined in the MTX
unit, but other documentation is a little scarce.
22 May 2001 Delphi Informant Magazine
Distributed Delphi
same transaction as the first component, MTS must be told.
This is done through the context object. Unfortunately this is
one aspect of the context object that isnt encapsulated in the
TMtsAutoObject class.
The object context interface has a CreateInstance method that
allows us to request the creation of another MTS object. This
will then be enlisted within the same transaction, if its transaction
model was marked Requires a transaction or Supports transactions. If
its transaction model is marked Requires a transaction, it will always
have a separate transaction.
Fortunately, TMtsAutoObject has a protected property,
ObjectContext, that we can access to get the interface to the
context object. The following statement creates an instance of
the MTSDemo1 component:
ObjectContext.CreateInstance(
CLASS_MTSDemo1, IMTSDemo1, obj);
Initialization
If theres any code that needs to be run to initialize an MTS component,
rather than placing it in the Delphi object constructor or the usual COM
Initialize method, it should be placed in the OnActivate method for the
MTS component. This is a protected method in TMtsAutoObject that
should be overridden in our component. This method is called by MTS
after the component has been created in preparation for a method call,
but before the actual call. Its always called before the method, even if the
component has been retrieved from the object pool (in COM+). Object
pooling is the reason for this; pooled objects should behave the same
whether they have just been created, or retrieved from the pool. This
makes it important that theyre initialized in the same way. Theres also a
Deactivate method where cleanup code can be placed.
Its important that exceptions are not allowed to escape from these
two methods. MTS doesnt expect initialization to fail, and so
does not handle this event very well. The client will just see a
catastrophic failure, which is neither informative nor reassuring.
This means that a try..except block must wrap the code in these
methods. Also, in the case of the OnActivate method, if the error
is to be handled, information about it must be recorded in the
component, and the method itself must check this and raise an
exception if required.
Object Pooling
If were developing components for COM+ and want to take advantage of object pooling, there are a number of steps we need to take.
The first is to use the Both threading model. The second is to override
the TMtsAutoObject.CanBePooled method to return True. By default
this switches pooling off.
Third, the object must be initialized to the same state, whether it has
just been created or retrieved from the pool. The last, and possibly
most difficult step, is to make sure the object is thread-safe, but that
topic is beyond the scope of this article.
23 May 2001 Delphi Informant Magazine
MTS Packages
What is a package or COM+ application on Windows 2000? MTS
components are in-process DLLs, so they need an application
process in which to run. For MTS this is mtx.exe, and for
COM+ this is dllhost.exe. If all components ran in the same
process space, any fatal exceptions would cause every application
running on MTS to fail. MTS limits the damage that this would
cause by grouping components into packages, each running as a
separate process.
A package can contain components from more than one DLL,
and a DLL can contain components that are in several packages.
The only restriction is that each component can only be in one
package. Besides providing process isolation, under MTS, packages
also define security boundaries. Calls into a package are checked to
Distributed Delphi
ensure the caller has sufficient privilege. Calls within a package are
not checked. In COM+, security checking can be done at a much
finer level, down to individual methods if required.
Debugging
We now have our application installed in MTS. How do we run and
debug it? First we need a client application that will call the component.
I always create a test bed application for each component (or group of
components). This greatly simplifies debugging and testing.
In the case of the MTSDemo1 component, this is a simple form
with the following components: DBGrid, DataSource, ADODataset, and Button. (The project was saved as MTSDemo1Lib.) The
code for the button can simply be:
procedure TForm1.Button1Click(Sender: TObject);
var
obj : Variant;
begin
obj := CreateOleObject('MTSDemo1Lib.MTSDemo1');
ADOTable1.Recordset :=
IUnknown(obj.GetData) as _Recordset;
end;
Deployment
Once the component has been fully tested, were ready to deploy
it. This is done using the Transaction Server Explorer for MTS, or
Component Services for COM+. These two tools are similar and
sport an Explorer-style interface, as shown in Figure 14.
MTS simplifies installation by allowing packages to be exported. To
do this, select the package in the left-hand tree view, right-click on it,
and select Export. This creates a package file and a copy of the DLL
that can be installed on other servers, complete with security settings
if required. It also creates a client installation program that can be run
on each client machine to connect it to this server.
This client installation program registers the COM component on
the client computer, greatly simplifying client installation. (Note: The
client install registers the server from which the export was run, so usually the package will be exported from the development environment,
and installed on the production machine first. Then its exported from
the production machine to create the client installation program.)
Once the package has been installed on the production system, the
transaction models for the components can be adjusted, and the
security set up. The MTS security model uses roles to group NT
users and groups. These roles are then granted access to components,
or even individual methods in COM+. This provides a secure
and flexible environment that can be controlled without changing
the components. The TMtsAutoObject methods, IsCallerInRole and
IsSecurityEnabled, can be used to provide finer control within the
component, but this is rarely necessary.
The creation of roles, the assignment of users and groups to them, and
the allocation of roles to components is done using the Transaction Server
Explorer, which makes extensive use of context menus.
Conclusion
Writing MTS components is straightforward with Delphi, providing a few simple precautions are heeded, and great care is taken
to produce robust, high-quality components. MTS provides a
number of services, from database connection pooling to security
checking. This can greatly simplify the task of creating a robust,
scalable middle-tier application to work with thin clients and
Web sites.
This article is based on a paper that was originally presented at the 11th
Annual Borland Conference, and is used with permission.
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2001\MAY\
DI200105MM.
Malcolm is a senior consultant with Dunstan Thomas Limited and has many years
experience designing and implementing client/server and distributed database
applications using both Microsoft and Borland technologies. As well as being a
Borland Certified Delphi Consultant, he is a Microsoft Certified Software Developer
(MCSD). Malcolm has consulted for clients throughout Europe and is currently
focused on developing Web-based systems using Delphi, MTS, and XML. He spends
his time away from computers being jumped on by the kids.
Greater Delphi
Excel Automation / OLAP / PivotTables / Database / Delphi 5
n a set of articles devoted to using Microsoft Office Web Components (in the December
2000 and January 2001 issues), you learned how to provide Delphi applications
with simple OLAP (online analytical processing) capabilities by using the PivotTable List
component to pivot, filter, and summarize information from various data sources and
present it in meaningful ways.
In this article, well continue to discuss implementing OLAP in Delphi applications, and outline
some advanced features of the Microsoft Excel PivotTable Services that are absent in the PivotTable
List component, but are available through Excel
Automation.
Greater Delphi
Application
Legend:
Object
WorkBooks
(WorkBook)
WorkSheets
(WorkSheet)
PivotTables
(PivotTable)
PivotFields
(PivotField)
Columns
(Column)
CubeFields
(CubeField)
Charts
(Chart)
Collections
PivotCaches
(PivotCache)
Chart
Chart Area
const
// Path to the Northwind.mdb file.
NW_Path = 'C:\Data\';
PC.CommandType := xlCmdSql;
PC.CommandText :=
'SELECT Invoices.Country, Invoices.City,' +
' Invoices.Customers.CompanyName,' +
' Invoices.ProductName, Invoices.Salesperson,' +
' Invoices.Shippers.CompanyName,' +
' Invoices.ExtendedPrice' +
' FROM "'+NW_Path+'Northwind".Invoices Invoices' +
' WHERE Invoices.Country = ''France'''+
' OR Invoices.Country = ''Germany''';
// Create a PivotTable.
PC.CreatePivotTable(
WB.Worksheets[1].Cells[3,1], 'PivotTable1');
PT := WB.Worksheets[1].PivotTables('PivotTable1');
CommandType
CommandText
Description
The string in the following form: <Data
Source Type>;<ADO Connecton String>,
where Data Source Type can be ODBC, OLE
DB, URL, or TEXT, depending on how Excel
should connect to the data source.
xlCmdCube OLAP cube
xlCmdSql SQL query
xlCmdTable Database table
xlCmdDefault Query text understood by
OLE DB provider
The text of a database query, or an OLAP
cube/table name
Greater Delphi
CREATE CUBE [OCWCube]
(
DIMENSION <Dimension Name> [TYPE <Dimension Type>]
LEVEL <Level Name>[TYPE <Level Type>],
[ LEVEL <Level Name> [TYPE <Level Type>]...],
[ [DIMENSION <Dimension Name> [TYPE <Dimension Type>]
LEVEL <Level Name>[TYPE <Level Type>,
[ LEVEL <Level Name> >[TYPE <Level Type>...],...],
MEASURE <Measure Name> FUNCTION <Function Name>,
[MEASURE <Measure Name> FUNCTION <Function Name>,...]
The CreatePivotTable method accepts two parameters. The first specifies the upper-left cell of the worksheet where you place the PivotTable, and the second specifies the name of the PivotTable object:
PC.CreatePivotTable(
WB.Worksheets[1].Cells[3,1], 'PivotTable1');
Greater Delphi
This results in a PivotTable with appropriate row, column, page, and
data fields inside the OLE container, as shown in Figure 5. By following this example, you programmatically created a PivotTable based on
a database query. In the next example, youll create a local OLAP cube
based on such a query.
This pivot cache will store data from an OLAP cube that you create. The
next step is to build a connection string to create and access this cube.
This string should consist of several parts. The first part defines the data
source that will point to the future cube: OLEDB; Provider=MSOLAP;
Initial Catalog=[OCWCube]; Data Source=c:\data\northwind.mdb.
Figure 9: The PivotTable based on the newly created OLAP cube.
PT.PivotFields('ProductName').Orientation := xlPageField;
PT.PivotFields('SalesPerson').Orientation := xlColumnField;
PT.PivotFields('ExtendedPrice').Orientation := xlDataField;
PC.CommandText := 'OCWCube';
Now, define the fields to be used for creating row, column, page, and
data fields of the PivotTable. In this case, use the CubeFields collection
of the PivotTable object. Please note that in Delphi you cant access the
CubeFields collection members by their names; access is only available to
them through their indexes:
PT.CubeFields[2].Orientation :=
xlRowField;
// Country.
PT.CubeFields[5].Orientation :=
xlColumnField; // Salesperson.
PT.CubeFields[4].Orientation :=
xlPageField;
// ProductName.
PT.CubeFields[6].Orientation :=
xlDataField;
// ExtendedPrice.
Greater Delphi
var
BMP: TBitmap;
...
if SaveDialog2.Execute then begin
// Copy the Chart area to Clipboard.
WB.Charts[1].ChartArea.Copy;
// Create a TBitmap object.
BMP:=TBitmap.Create;
// Load Clipboard content into it.
BMP.LoadFromClipboardFormat(cf_BitMap,
ClipBoard.GetAsHandle(cf_Bitmap),0);
// Save bitmap to the specified file.
BMP.SaveToFile(SaveDialog2.FileName);
// Free allocated resources.
BMP.Free;
end;
sures, or specify the data source on which the cube is based. To open a
local cube, you need to specify the ADO connection string to access the
cube that contains the OLE DB provider name (in this case, its the OLE
DB provider for OLAP Services), and the name of the .cub file:
PC.Connection := 'OLEDB;Provider=MSOLAP; ' +
'Initial Catalog=[OCWCube]; Data Source=C:\MyCube.cub';
PC.CommandType := xlCmdCube;
PC.CommandText := 'OCWCube';
Conclusion
If you need to open a cube created with SQL Server 2000 Analysis
Services, you need to specify the appropriate connection string:
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2001\MAY\
DI200105AF.
To open an OLAP cube created with SQL Server 7.0 OLAP Services,
you need to specify the ADO connection string to access the OLAP
server and the OLAP cube name:
PC.Connection := 'OLEDB;Provider=MSOLAP.2;' +
'Data Source=maindesk;Initial Catalog=FoodMart 2000;'
PC.CommandType := xlCmdCube;
PC.CommandText := 'OCWCube';
Alex Fedorov is a Chief Technology Officer for Netface SA, based in Lausanne,
Switzerland (http://www.netface.ch). Hes one of the co-authors of Professional
Active Server Pages 2.0 [Wrox Press, 1998] and ASP 2.0 Programmers Reference [Wrox Press, 1999]. Natalia Elmanova, Ph.D., is an executive editor for
ComputerPress magazine published in Moscow, Russia (http://www.compress.ru).
She was a contributing author to the 10th and 11th Annual Inprise & Borland.com
Conferences. Natalia and Alex are authors of Advanced Delphi Developers Guide
to ADO [Wordware Publishing, 2000], and several programming books written in
Russian. You can visit their Web site at http: //d5ado.homepage.com.
Sound+Vision
OpenGL / 3D Graphics / Delphi 4, 5
By Eli Bar-Yosef
+Y
-Z
+X
-X
+Z
-Y
Figure 1: 3D coordinate system.
OpenGL
OpenGL (Open Graphics Library) is a software
interface to graphics hardware that helps to
program 3D applications. The OpenGL API
includes about 300 commands with which you
can construct a 3D object from small geometric
primitives such as dots, lines, triangles, etc., as
well as modeling objects, pouring light, adding
texture, etc.
Initiated by Silicon Graphics (SGI), OpenGL is
supported by many large companies such as Microsoft, Compaq, and HP and can be used on most
popular operating systems such as Windows, Mac
OS, Linux, and others. Currently there are several
software interfaces to graphic hardware. DirectX,
developed by Microsoft, and OpenGL are the most
well known.
In my humble opinion, DirectX has more drawbacks than OpenGL. First, to use DirectX you
must be COM-savvy (admittedly not a problem
for most Delphi developers). Second, you cant port
your applications to other operating systems; your
application can only run on Windows. Third, I
find the DirectX API a little cumbersome compared to the OpenGL API.
Now that you know what OpenGL is and what
you can achieve by using it, lets look at 3D
programming concepts in general, then OpenGL
syntax and commands.
Sound+Vision
Vertices and Matrices
Our world is three dimensional: Every object can be represented on
three axes: the x, y, and z axes. So when you build your 3D objects,
you should always think of your screen as a coordinate system that
has an x, y, and z axis.
As you can see in Figure 1, on the right side of the screen you have the
positive x axis, on the left the negative x axis. Up is the positive y axis,
and down is the negative y axis. You can also see that when you move
inside the screen you have the negative z axis, and out of the screen,
you have the positive z axis.
glBegin(GL_LINES);
glVertex3f( 2.0, 2.0, 0.0);
glVertex3f(-2.0, -2.0, 0.0);
glEnd();
The vectors in the first three rows are unit vectors that define directions. More specifically, the first row represents the 3D coordinates
space of the local x axis, the second on the y, and the third on the z.
The three rows collectively represent the rotation of the object. The
last row represents the objects position (translation) in the 3D space.
Theres another thing you must keep in mind when you deal
with matrices: Matrix multiplication is not commutative. In other
words, the order of the operations you perform on the matrix is
important. This concept will be clearer in a later section when we
discuss transformation.
Now that you understand some of the 3D programming concepts,
lets look at the OpenGL
API. (For in-depth coverage
2, 2 of 3D concepts, check the
links at the end of this article. Some of the links are for
sites containing numerous
articles regarding vectors,
matrices, collision-detection
algorithms, Artificial Intelligence (AI), and so on.)
-2, -2
Figure 3: A line.
31 May 2001 Delphi Informant Magazine
Objects
Objects are one of the
basic building blocks of
your scene. OpenGL pro-
As you can see from this code, the drawing starts with
glBegin(GL_LINES). This means that we start our drawing and specify which primitive shape we would like to draw. In this case, we
stated that we wanted to draw a line by using GL_LINES. Then we
specified to OpenGL that we want to draw two vertices. That has
been done by using the function glVertex3f(x, y, z).
The 3 in this function means were using a function that needs the
three parameters x, y, and z. The f means well use a function that will
get a floating point number as an argument. If you check the OpenGL
API documentation, youll notice several functions that are similar to
this function, but each has different parameters. For instance, when you
want to draw a 2D line, you would normally use glVertex2i(x, y). The
2 indicates this function takes only two parameters, x and y; the suffix
i means you must supply the parameters as integers.
Then, to conclude the drawing, its necessary to call glEnd. Now you
know how to draw a simple line. Next, lets draw a simple polygon.
A polygon is a shape built from several lines or dots, and closed in a
specific area. When you build a polygon, there are two rules:
the lines cannot intersect
the polygon must be convex
This code describes a simple polygon with four vertices:
glBegin(GL_POLYGON);
glVertex3f(-1.0, -1.0,
glVertex3f(1.0, -1.0,
glVertex3f(0.5,
0.5,
glVertex3f(-0.5, 0.5,
glEnd ();
0.0);
0.0);
0.0);
0.0);
Colors
When youre drawing an object using OpenGL, you may want
to specify its color. OpenGL has a special function for specifying
the object color, named glColor3*(...), and it takes red, green, and
blue (RGB) as arguments. The asterisk means this function has
Sound+Vision
+Y
+Y
+Y
+Y
+Y
-X
+X
-X
+X
-X
+X
+X
-X
-Y
-Y
-Y
-Y
-Y
1. Initial state
3. Rotate 45 degrees
around the Z axis
0.0);
0.0);
0.0)
0.0);
1.0);
0.0);
0.0)
0.0);
// red
// yellow
// blue
// green
Transformations
Now you need to set the position and orientation of the object youve
created. In other words, its time to deal with transformations. Think
of the transformation process on the computer as you would a camera:
You need to set the camera properties of orientation, position, and lens
type. When you want to view the scene you need to specify:
Viewing
Modeling
Projection
Each of these transformations is represented by a 4x4 matrix. To
achieve the desired effect on your scene, you will have to multiply
your current matrix by the matrix youve constructed. The formula
is V = MV, where M represents your transformation matrix, and V
is the current matrix.
When you implement the transformation on your 3D scene, you
must implement it in a specific order, or you wont get the desired
results. The order to use is Viewing, Projection, and Modeling.
32 May 2001 Delphi Informant Magazine
Before you change the transformation of your scene, you must tell
OpenGL by which matrix it should multiply your 4x4 matrix. You
can do that by using the procedure glMatrixMode(mode: GLenum). The
mode can be one of the three values: GL_ModelView, GL_Projection, or
GL_Texture. Where is the GL_View value? The Viewing transformation
is the same as the Modeling transformation. It will soon become clear.
Again, when youre dealing with the Viewing transformation, think of
it as positioning a camera on your object. Lets say you want to move
the camera two units to the right to change the view so it will show the
object from a 45-degree angle. To do this you can use the command:
procedure gluLookAt(eyex: GLdouble; eyey: GLdouble;
eyez: GLdouble; centerx: GLdouble; centery: Gldouble;
centerz: GLdouble; upx: GLdouble; upy: GLdouble;
upz: Gldouble);
This command can help you to build a 4x4 matrix for performing the
transformation. Notice in the previous code that glu* was used, not
gl*. Thats because this function is called from the GL Utilities (GLU)
library. This library has a nice collection of functions that might help
you accomplish some tasks more smoothly.
To use gluLookAt(...) you have to supply three groups in which each
group contains three points. The first is eye*(x, y, z), for the position
of the camera on the screen. The second is center*(x, y, z), for the
direction of the camera lens. The third is up*(x, y, z), which sets the
orientation of the camera.
Modeling Transformation
Modeling transformation is used to set your objects position and
orientation inside the scene. To achieve Modeling transformation,
OpenGL offers the use of three special Modeling transformation procedures: glTranslate*(x, y, z), glRotate*(Angle, x, y, z), and glScale*(x, y,
z). glTranslate*(x, y, z) lets you move your object any direction on the
screen. Lets say you want to move the polygon weve constructed three
moves to the right side of the screen, and 10 moves inside the screen.
The following code achieves this:
glMatrixMode(GL_ModelView)
glLoadIdentity();
glTranslatef(3.0, 0.0, -10.0);
glBegin(GL_POLYGON);
glVertex3f(-1.0, -1.0, 0.0);
glVertex3f( 1.0, -1.0, 0.0);
glVertex3f( 0.5, 0.5, 0.0);
glVertex3f(-0.5, 0.5, 0.0);
glEnd();
Sound+Vision
+Y
+Y
+Y
+Y
-X
+X
+X
+Y
+X
-X
+X
-X
-X
-X
-Y
+X
-Y
-Y
-Y
-Y
1. Initial state
2. Rotate 45 degrees
around the Z axis
The previous code sets the current matrix to be the model view
matrix. Next we need to clear the model view matrix. This is
done by multiplying the current matrix by the identity matrix.
Then we call the glTranslatef(...) procedure to help us to construct
a matrix that will be implicitly multiplied by the model view
matrix. Eventually, this will affect the position of the object in
the scene.
glRotate*(angle, x, y, z) lets you rotate the object in a specific angle,
and around a specific axis. As shown previously, when you call
glRotate *(Angle, x, y, z), you actually construct a matrix that is
multiplied by the current matrix. For instance, if you want to
rotate an object 30 degrees around its x axis, use glRotatef(30.f,
1.0, 0.0, 0.0).
glScale*(x, y, z) lets you shrink, stretch, or reflect an object along
its axis. glScale(...) takes three arguments. The arguments construct
a matrix that will be multiplied by the current matrix, the model
view matrix. Lets say you want to shrink an object. You would use
glScalef(0.5, 0.5, 0.5) to make an object half its size.
Again, matrix multiplication isnt commutative, so its critical to pay
attention to the order in which you multiply your matrices, as Figures
4 and 5 demonstrate.
Projection Transformation
Projection transformation specifies how your object will be projected
on the screen. In addition, it helps OpenGL decide which parts of
your objects should be clipped from the final image. There are two
ways to tell OpenGL how to project objects to the screen: perspective
and orthographic projection.
In this article, only perspective projection will be discussed. Perspective means that your objects get smaller or bigger depending
on the distance of the objects. This effect occurs because the
perspective projection has the shape of a truncated pyramid called
a frustum. The OpenGL library contains the glFrustum procedure
to specify this:
procedure glFrustum(left: Double; right: Double;
bottom: Double; top: Double; near: Double; far: Double);
As you might imagine, the bottom and left, and top and right arguments
define the lower-left and upper-right corners of the frustum, respectively;
33 May 2001 Delphi Informant Magazine
and near and far set the depth of the clipping plane. Now that you know
how to use the glFrustum(...), lets look at another command named
gluPerspective(...), the counterpart of glFrustum. Its defined in the GLU
library and is somewhat easier to use than the glFrustum procedure:
procedure gluPerspective(fovy: Double;
aspect: Double, near: Double; far: Double);
Conclusion
Thats it for this month. Practice making 3D objects, coloring them,
and transforming them. And watch out for the order of matrix
multiplication! Next month, well explore how OpenGL handles
lighting, materials, texture, and more. Ill also make available some
Delphi classes for manipulating OpenGL, and an example application that shows them off. See you then.
Important Resources
Check out http://www.OpenGL.org; it contains lots of usable
information about 3D programming and a huge number of
great links to other 3D programming sites. Also look into
http://www.nvidia.com for dazzling demonstrations on 3D
programming using OpenGL.
At Your Fingertips
Qt / Cross-platform Development / Linux / Kylix
By Bruno Sonnino
any tips presented in my At Your Fingertips column use the Windows API and
cannot be ported to Kylix. This month, however, well convert some tips to Kylix
using the Qt API. This conversion will give you an added benefit; theyll be portable to
Windows when Delphi 6 is released (Borland engineers report that CLX and the Qt API
will be included in Delphi 6). And, its also possible theyll be portable when (tongue
planted firmly in cheek) Delphi for Solaris, Delphi for Mac, Delphi for Palm, and other
Delphi platforms are launched.
At Your Fingertips
use any Qt API function, you must include the Qt unit in your
uses clause. Then use any of the overloaded constructors (see
Figure 3) to create a region using the Qt API.
The second and third constructors create an elliptic or a rectangular region, depending on the value passed in the last parameter.
QRegionRegionType is defined like this:
first parameter is the forms handle, and the second is the handle to
the region or to a bitmap (more on this later):
procedure QWidget_setMask(handle: QWidgetH;
p1: QBitmapH); overload; cdecl;
QRegionRegionType = (QRegionRegionType_Rectangle,
QRegionRegionType_Ellipse);
With this brief introduction, you can convert the Windows program
shown in Figures 1 and 2 by putting the code in the forms OnCreate
event. Figure 4 shows the code to draw a round window; Figure 5
illustrates the result.
At Your Fingertips
Figure 7: Result of
using QRegion_unite.
Figure 9: Result
of using
QRegion_subtract.
Figure 8: Result
of using
QRegion_intersect.
Bitmap-shaped Forms
Another way to create a non-rectangular form is using a bitmap mask.
You may have noticed the QRegion constructor at the beginning of
the article receives a QBitmapH. Dont be fooled by the name: a
QBitmapH is not the handle of a TBitmap (that would be too easy).
The handle of a TBitmap is a QPixmapH, which points to a mask
of monochromatic bits. In this format, one bit represents each pixel.
The region will be created using the bits that are turned on.
You can create a bit mask manually using a bit array, but theres
a better way: The TBitmap class has a method named Mask that
receives a transparent color and converts the bitmap into a bit mask.
36 May 2001 Delphi Informant Magazine
To create the mask, you must load the bitmap, creating the mask with
the TBitmap.Mask method, and make a region from it. This is done
in the forms OnCreate event, as shown in Figure 12.
The first thing to do is to create a bitmap, and load the bitmap file.
Remember that file names are case-sensitive in Linux, so take care with
the file name used in LoadFromFile. Then, convert it to a monochrome
mask, passing the color you want to be transparent. This color wont be
drawn in the final form. Finally, create a region from this bitmap.
Although you used the Mask method to convert the bitmap to a
mask, Bitmap.Handle still is a QPixmapH and, to retrieve its mask as
a QBitmapH, use the QPixmap_mask function. This function returns
a QBitmapH with the mask of the bitmap. When you run the project,
you should get a form like that shown in Figure 13.
The function in Figure 12 is simple, but you can simplify it a bit
more. When you saw the two forms of QWidget_setMask, you probably saw you could use a region or bitmap. Using a bitmap, theres
At Your Fingertips
Conclusion
Working with the Qt API is easy and has the added advantage of
being portable between platforms (as soon as Delphi 6 ships). In
other words, if you want portability, you must use CLX and Qt API,
instead of the Windows API.
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2001\MAY\
DI200105BS.
Then, you must draw the bitmap. The best place to do it is in the forms
OnPaint event handler. Use the Canvas.Draw method, as shown here:
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Draw(0,0,FBitmap);
end;
A Brazilian, Bruno Sonnino has been developing with Delphi since its first version
in 1995. He has written the books 365 Delphi Tips, Kylix - Delphi for Linux, and
Developing Applications in Delphi 5, published in Portuguese. He can be reached
at [email protected].
XTNDConnect RPM
Quickly Produce Thin-client Solutions
In brief, XTNDConnect RPM simplifies the delivery of data. Corporate business rules and processes
are embedded at the RPM server rather than the
client, leading the way to exceptionally thin clients,
and allowing for the widest range of connectivity
and solution choices.
XTNDConnect RPM (Remote Procedure Middleware) is an embedded server that makes mobile
and Windows thin-client application development
easier. Its installed between your database or application servers, and a wide variety of client endpoints such as a Palm Pilot or desktop PC.
This connectivity isnt limited to database applications; any business process that lends itself to a programmatic solution can be controlled through a client
application, including report building, faxing, and
printing. Using the RPM client components with a
companys existing code base is a matter of identifying
those processes that can be removed from the client
software and migrated up to a process server.
Conclusion
Figure 4: The client code doesnt define business rules.
compilation will produce a DLL file. This DLL file is what will be
executed by the RPM server when needed by your client.
With the business rules embedded in the server, the development of
the client portion of your application is vastly simplified. The client
code wont define any business rules itself; it will merely be a shell
providing an input/output interface for the user. Notice the lack of
editing details in the example procedure shown in Figure 4.
After adding an rpmContainer and rpmServer component to the
applications form, the first few lines of code will associate them with
the RPM server. For example, the statements:
with rpmContainer1 do begin
ContainerName := 'Invoice_1';
ProcedureName := 'CreateInvoice';
identify the RPM container and the specific rule from that container
which is to be processed (a container can contain numerous rules).
The subsequent statements set up the parameters to be passed to the
remote procedure, execute it, and display the results.
This example oversimplifies the complexity of an application of any
measurable use, but it does point out the ease of use of the XTNDConnect RPM design. The learning curve is initially steep, but is
quite short. Taking the time to study the well-documented manual
provided on the disk, and understanding the service and role that
XTNDConnect RPM performs makes its use almost intuitive. All the
examples provided by Extended Systems help the learning process.
Beyond Delphi
XTNDConnect RPM supports Borlands C++Builder in the same
fashion as Delphi. If your programming activities will include development for the Palm OS, Extended Systems is ready to help you. Two
popular Palm development environments are supported: Penright!
MobileBuilder, and Metrowerks CodeWarrior. Support for the two
development tools is provided in the form of a C API or C++ classes.
40 May 2001 Delphi Informant Magazine
Warren Rachele is Chief Architect of The Hunter Group, an Evergreen, CO softwaredevelopment company specializing in database-management software. The company has served its customers since 1987. Warren also teaches programming,
hardware architecture, and database management at the college level. He can be
reached by e-mail at [email protected].
ften, applications may only need to offer a user few options for lists, labels, or
reports, if there is a need at all. Many times, the options offered may only consist
of a limited set of predetermined, preformatted documents. However, some applications
must offer more comprehensive services, especially when a user expects to be able to
design custom documents to fit their specialized needs.
The requirements for listing, labeling, and reporting services may vary greatly from a simple selection on a dialog box to full design capabilities by
the user. There are numerous tools that may be
used with, or in, Delphi applications that allow
you to create these services for your users. Many of
them only allow you to create preformatted documents or documents with limited design options.
A Distinct Difference
What really caught my eye when using List & Label was the Report
Designer, a visual designer that provides basic desktop publishing
functionality (see Figure 1). Whether its you, the developer, creating
reports, lists, or labels for your end users, or you making the designer
available in your applications for your users to access, the Report
Designer provides a comprehensive GUI that speeds the process. All
your data and variables are visible from a tool window that allows
you to simply drag and drop them onto the document. Placement of
objects in the designer is quick and precise.
The variables list that I mentioned is represented in a well-organized
TreeView. You can extend the list and add more variables and expressions as needed. Assistance is provided in a dialog box that allows you
(or your user) to utilize predefined expressions and functions, or you
can build your own. Many of the predefined Avery label templates
are provided as well.
I liked what I saw in List & Label, namely the Report Designer where
I could create predefined reports, lists, and labels right in the Delphi
IDE, and at design time. Even more appealing is being able to ship a
visual designer as part of my application so users may create their own
documents. However, it may be a little difficult for some end users.
combit GmbH
Untere Laube 30
78462 Konstanz
Germany
Phone: (49) 7531 906010
Fax: (49) 7531 906018
E-Mail: [email protected]
Web Site: http://www.combit.net
Price: List & Label 7.0 for
Windows 95/NT 4, US$501.07;
Delphi-only, US$357.90.
Distributed in the
United States by:
BeCubed Software, Inc.
170 South Church Street
Canton, GA 30114
Phone: (888) becubed
Fax: (770) 720-1078
E-Mail: [email protected]
Web Site: http://
www.becubed.com
tool kit than to require my users to pay ongoing royalties. Fortunately, there are absolutely no royalties for List & Label, including
the Report Designer. Plus, the cost is very reasonable. You can
purchase the Delphi-only version of List & Label for US$357.90
(at the time of this review) from the combit Web site at http://
www.combit.net.
Support is free for the first thirty days, starting at the time of your
initial support request. Afterward, combit charges for support with
a Point Card system (see their Web site for details). This includes
written support requests such as fax, BBS, or e-mail, as well as
telephone support.
Conclusion
I liked what I saw in List & Label, namely the Report Designer
where I could create predefined reports, lists, and labels right
in the Delphi IDE, and at design time. Even more appealing is
being able to ship a visual designer as part of my application so
users may create their own documents. The built-in wizards and
assistants were also helpful.
However, there are a few tradeoffs to consider when incorporating
List & Label. Along with the complex document creation and other
capabilities List & Label has to offer, it may be a little difficult for
some users to grasp (if you ship it with your applications), even with
the Report Designer. This in turn may require you to offer some
additional training for purchasers of your applications who may not
be computer savvy. If you are in the market for such a tool, I suggest
you try the demo of List & Label. It might just be what youre
looking for.
Best Practices
Directions / Commentary
As Simple as Possible
hat does ASAP stand for? You may be familiar with the phrase as soon as possible as an answer to, When
does the software project need to be finished? I find that a more important phrase for programmers to learn
is as simple as possible. If software is kept as simple as possible, it will among other benefits increase your
chances of getting it done as soon as possible.
Simplicity is important, both in the user interface and the code
beneath it. Keeping things simple isnt the sign of a simple mind.
Its easy to make something difficult to understand; it requires much
more intelligence to make something that is difficult seem easy, or
at least approachable.
Due to time constraints or seemingly unique requirements, we may
feel justified at times using out-of-the-ordinary UI designs. Be careful!
Users are accustomed to certain ways of doing things Windows
usage patterns, if you will. Windows programs have a certain look and
feel, and modus operandi, that we would do well to adhere to as much
as possible. If we confuse the user, we make things more difficult
for ourselves in the long run. And not only for ourselves also for
trainers, and those who support the end users.
The foundation behind the facade is the code, which the end users
will probably never see. Your audience is admittedly a more technical
one, but even here its beneficial to keep things ASAP. As youre
coding, you have the high level, overall view firmly in your mind, and
are also concentrating on the low-level intricacies of the implementation. The code seems readable enough now. But what about in a
year, or even a month, or a week when you look at it again?
Im sure most of you have had the same experience when returning
to maintain previously written code confusion and consternation.
What does this method do? And why? Why was it implemented this
way? Why wasnt it done this way instead? You can save yourselves
and others a lot of grief, agony, frustration, and time by keeping your
code ASAP. Albert Einstein probably said it best when he advised,
Things should be made as simple as possible, but not any simpler.
Of course, ASAP means as simple as possible while producing software that your sponsors, employer, client, or users consider successful.
The traditional Hello, World program is simple (see http://
www.ariel.com.au/jokes/The_Evolution_of_a_Programmer.html for a
poor example of ASAP), but it probably wouldnt meet the needs of
even your least demanding customer or employer.
A potential roadblock to keeping your programs ASAP is the tendency of management to allow or demand feature creep to
muddy the waters. They often think adding more bells and whistles
will equate to more revenue. We coders are also often our own worst
enemies as a result of our tendency to gold plate adding cool,
but unnecessary features. If we were to be honest with ourselves, our
prime motivation in adding some features is our fascination with the
how of providing the feature, rather than a conviction of its true value
to the user. What we think is cool may be superfluous to the user.
By now youre hopefully convinced of the necessity, or at least
the desirability, of keeping your code ASAP. So how can you go
about doing that? Three simple ways: Avoid inconsistent or extreme
formatting/indentation styles, steer clear of cryptic or nonsensical
method and variable names, and shun very long methods.
As far as formatting style goes, a good pattern is provided by Borlands own source code. You will notice the balance they use, for
example, in the number of spaces they indent it is neither miniscule (one space, as in some code Ive maintained), nor does it go to
the other extreme of six spaces.
As to method and variable names, I have seen a variable named
IsValid. A Boolean, right? In fact, it was a global string variable! Im
sure you have seen function names that sounded like procedures, and
vice versa. Make the purpose of your variable and method names as
obvious as possible by using clear and accurate names.
Another way programmers commonly make matters overly complex and
confusing is by writing long blocks of event handler code. It is preferable
to make calls to procedures and functions (perhaps in another unit) in
these cases, making the event handler code ASAP, so the maintainer can
see at a glance whats supposed to happen in response to that event.
Each method should be atomic; it should do one thing and one thing
only. If several related operations need to be performed, break them into
separate methods, and have them call each others services as needed.
The bottom line: If you want to win friends and influence people
with your software, keep it ASAP.
Clay Shannon
File | New
Directions / Commentary
ast month we returned to the topic of Quality First (first addressed by me in this column in the July 1998 issue) by
examining three excellent books that discuss various aspects of ensuring quality in our development projects. We also
took a look at the concept of quality in the beginning, or how to lay a proper foundation for such a project.
This month well continue to explore further insights from Code
Complete [Microsoft Press, 1993] and After the Gold Rush [Microsoft
Press, 1999] by Steve McConnell and Constructing Superior Software
[Macmillan Technical Publishing, 1999, Paul C. Clements, ed.]. Lets
turn our attention to what happens in the middle stage of creating
new software.
Quality in the middle. The middle part of a software development
project generally revolves around coding. Its worth repeating some
of Bertrand Meyers insights from my July 1998 column. Recall that
Meyer recommends you build [an application] so you can trust it.
Then dont trust it. He further recommends beginning with the
cosmetics, by which he means using correct syntax, writing readable
and well-commented code, and sticking to a consistent coding style.
More importantly, he recommends we get everything right from the
start and fix it immediately if it is not.
A complex project might also include some design tasks. Of course,
in Barry Bohems spiral process described in Chapter 3 of Constructing Superior Software, the seemingly preliminary task of planning, analysis, and design continue throughout a project, becoming
more refined when informed by results of implementation tasks.
McConnell points out in After the Gold Rush that ideally, although
planning and process-management continue throughout a project,
emphasis on this should be high at the start, then decrease as the
project continues.
Emphasis on quality should be just as high during the implementation/
coding phase as in the initial planning phase. This means that one
should not only use best practices in coding, but also be willing to go
back and modify the architecture or design, if a flaw becomes apparent. This is much less costly in wasted time if its done immediately.
During the middle phase, we should also introduce a task normally
associated with the end of a project: testing and debugging. Following
Meyers precept previously quoted, our goal should be to remove
most defects before we release any software. In After the Gold Rush,
McConnell states that projects that remove about 95% of their
defects prior to release are the most productive; they spend the least
time fixing their own defects.
44 May 2001 Delphi Informant Magazine
File | New
Another task that is worth considering at the end is a thorough analysis
of the entire project. Such introspection can pay great dividends for
future development of the current project or the company as a
whole. Developers and testers can identify shortcomings that will be
addressed by new features in future versions, make certain that support
personnel have sufficient information to address customer needs, and
make absolutely certain that the product is ready to ship. An endof-project assessment also provides an opportunity for a company to
improve its overall effectiveness, moving through the levels of the Software Engineering Institute Capability Maturity Model for Software that
McConnell discusses in After the Gold Rush. Such analysis would be
largely absent at the first two levels: initial and repeatable. At this second
level, the emphasis is about getting beyond the code-and-fix approach.
Such an assessment might be introduced by an organization thats at
the defined phase, characterized by standardized technical and management processes. It would certainly be present in the most mature
organizations, those that are managed ... to evaluate the effectiveness
of different processes and optimizing organizations that emphasize
improving the processes themselves by engaging in constant assessment
at all levels. Clearly, an emphasis on quality throughout a project can
impact not just the work at hand, but future endeavors as well.
Conclusion. We can help ensure quality in the beginning by not
jumping too quickly into coding, thus avoiding the code-and-fix trap.
Newer tools such as design patterns and UML can be helpful (this will
be the topic of an upcoming column). We can ensure quality in the
middle by using best practices in our coding. There is hardly a better
single source of best practices than McConnells Code Complete. Of
course, Delphis component-based, object-oriented structure gives us a
mighty advantage. We should also consider an evolutionary prototyping approach in which we incorporate testing and debugging early in
a projects development. We can ensure quality at the end by making
certain that the product to be released has been tested by a variety of
users in a variety of environments. And before we finish, we should
take a long look back to assess all of the procedures we have used from
the start, so we can work more efficiently next time.
I hope that you have found these ideas helpful in your work; but
please bear in mind that most of them are borrowed ideas on which
I have elaborated. I have relied heavily on the three excellent books
described throughout this two-part series; I highly recommend each
one. Until next time....
Alan C. Moore, Ph.D.