May 2000, Volume 6, Number 5: On The Cover
May 2000, Volume 6, Number 5: On The Cover
May 2000, Volume 6, Number 5: On The Cover
ON THE COVER
6
On the Net
FEATURES
13
Delphi at Work
18
Dynamic Delphi
23
30
Greater Delphi
DEPARTMENTS
2
5
34
36
Delphi Tools
Newsline
Best Practices by Clay Shannon
File | New by Alan C. Moore, Ph.D.
Delphi
T O O L S
New Products
and Solutions
Extended Systems Releases RPM Server and Crystal Reports Driver for Advantage
Database Server
Extended Systems, Inc.
released RPM Server (Remote
Procedure Middleware) and
Crystal Reports Driver for
Advantage Database Server.
RPM Server is a middle-tier software solution that allows application developers to move intensive
database processing off the client
application and onto the database
server. RPM Server complements
Advantage Database Server, the
companys SQL client/server software, by acting as a workhorse
for thin clients. The combination
provides an alternative to traditional two-tier database applications, where client applications
perform much of the applications
custom database activity. RPM
Server offers a way to deploy
and maintain code at a single
point, providing greater stability
to the application and increased
royalty free.
Features include a COM object
for ASP pages that will return the
number of pages in a multi-page
.tiff; the ability to recognize and
return sizes of varied size pages
within a multi-page .tiff; fewer
DLLs required; the ability to specify the size of an image; the UNC
path name feature, allowing files
on different computers within a
system to be accessed by the same
server; and password protection.
For photographic images, Docto-Net supports .bmp, .pcx, and
.tga, and presents them as .jpeg,
.png, or .gif files. Image correction
Delphi
T O O L S
New Products
and Solutions
Blinkinc
Price: US$299
Phone: (804) 784-2087
Web Site: http://www.blinkinc.com
Delphi
T O O L S
New Products
and Solutions
News
L
May 2000
smaller, independent book publishers, such as Wordware, Manning, and Informant Press.
Computer professionals and
novices alike are looking for a comprehensive, competitively-priced
source for IT-related books and
training materials, said Mitchell
Koulouris, president and CEO
of Informant Communications
Group. ComputerBookstore.com
is the perfect resource for experts
in information technology, programming, database development, and related technologies
and industries.
In addition to the vast selection of books, training materials, videos, and documentation,
ComputerBookstore.com guarantees a savings of up to 41per-
On the Net
By Richard Phillips
recently bought one of the new digital satellite dishes and ran across an interesting
challenge figuring out just what was on, and when. DirectTV provided a Web site
with a basic search engine, but the Web-based search was slow and very narrowly
focused. As a typical programmer, I knew I could provide a better, more powerful UI, if I
could just figure out how their Web search engine worked.
A quick scan of the HTML source pointed out
a relatively simple search form that I could easily
duplicate, but the HTML results came back in
a mildly complicated HTML table. Brute force
code would have been simple enough to construct to parse through the table, but Id been
looking for a reason to build a more general
parser, so off I went. If Id known just how lax
the HTML rules are, and just how many hacks
there are, Id have just stuck with the brute force
method and saved myself a lot of agony, but
since Im here now ...
The Basics
To put together a parser for HTML, an understanding of the rules is required. HTML originated
as an SGML-like syntax, and over time has grown
to fit more closely within the confines of SGML.
These days the syntax is described within an
SGML Data Type Definition (DTD), bringing it
into a reasonably well-understood and managed
domain. Given that SGML now establishes the
underpinnings of HTML, the parser should apply
the SGML syntax rules as a starting point. This
also allows for the consideration of some simple
extensions that allow parsing of XML.
Therefore, the parser is built to work on SGML in
general, with specific handlers for exceptions and
extensions that occur in HTML and XML. The
rules for SGML are straightforward, and provide
five basic constructs that we care about:
elements,
attributes,
comments,
6 May 2000 Delphi Informant Magazine
HTML Extensions
On the Net
tag, all omitted end tags are considered closed back up to the
matching start tag. Also, by observation (I couldnt find a formal
rule for this in the HTML specification), virtually all elements with
optional end tags close when they encounter another of themselves,
<LI> and <OPTION> being fine examples. Further, the HTML
reference material does indicate that <P> elements that omit their
end tags are terminated by block elements. The HTML DTD must
be consulted to determine which elements are considered block elements. Unfortunately, all of this prevents us from using a general rule,
and requires that we become concerned with the HTML DTD.
A quick consideration of the DTD is therefore in order. The DTD calls
out which elements must have end tags, and which may omit (or are
forbidden to have) end tags. For example, the DTD fragment for <P> is:
Comments
SGML also provides that its content may include comments. Comments are of the form:
<!-- This is a comment -->
<!-<
The important thing to note here is - O. The - indicates the start tag
is required, and O means the end tag may be omitted. Compare this
to the fragment for <BR>:
<!ELEMENT BR - O EMPTY >
where - O EMPTY indicates that the start tag is required. Since the
element is EMPTY, however, the optional end tag is now expressly
forbidden. In the fragment for <P>, the (%inline;)* shows the
legal contents of the P element. In this case, %inline; refers to a
list of elements defined earlier in the DTD. Notably missing from
the %inline; list is <P> itself. A perusal of the other ambiguous
elements reinforces the observation that in general, an element may
not immediately contain itself (although this ambitiously general rule is
certainly not guaranteed to remain valid for future releases of the HTML
DTD). In the same way that %inline; is defined, so there exists a list
name %block;, which contains the list of block elements.
This leads to another set of parsing rules:
<P> with an omitted end tag is terminated when a block element
is encountered.
Elements are terminated when another element of the same name
is encountered.
Elements are terminated if a parents end tag is encountered; no
spans are allowed.
Attributes
Attributes represent the various properties of elements. By definition, attributes appear in name/value pairs within the start tag of
the element. For example, in:
<BODY bgcolor="#FFFFFF">
the BODY element has an attribute name bgcolor, and the attribute
has a value of #FFFFFF. Double quotation marks or single quotation
marks are required to delimit the value, unless the value contains
only letters, digits, hyphens, and periods. If the value contains single
quotation marks, it should be delimited with double quotation
marks, and vice versa. Attribute names are case-insensitive. Also
worth noting is that not all attributes have a value. For instance, the
NOWRAP attribute of the <TD> element.
Attributes appear within an elements start tag
Attributes are delimited by a space character (ASCII 32)
Attribute values are delimited by "or '
7 May 2000 Delphi Informant Magazine
XML Extensions
Everything Else
On the Net
<HTML>
<HEAD>
<TITLE>Example HTML</TITLE>
</HEAD>
<BODY>
<!-- Insert non-sensical comment here -->
<H1>Example HTML</H1>
Plain old text right out there in the middle of the document.
<P>Text contained within in paragraph</P>
<B>Unordered List</B>
<UL>
<LI>Item #1
<LI>Item #2
<LI>Item #3
</UL>
</BODY>
</HTML>
Additional Considerations
It is also worth noting that syntax errors and occurrences of browsertolerated HTML inconsistencies are frequently encountered, and as
such, should not raise exceptions except in extreme cases. Instead, a
warning should be noted and parsing should continue if possible.
The Parser
The goal of parsing the SGML content is to place the data it represents into a form more readily accessible to other components.
Because SGML calls out a hierarchical structure, a hierarchy is
probably the most accurate way to store the parsed content. With
that in mind, the parser is built from two primary classes, and a
third supporting class:
First and foremost is the THTMLParser class. Its Parse method
accepts the content to be parsed, and places the processed results
in the Tree property.
Next is the TTagNode class in which the parsed results are contained. This class is a hierarchical storage container with Parent
pointing to the TTagNode that contains the current node, and
Children containing a list of children immediately owned by the
current node.
TTagNodeList is provided as a list container for a collection of
TTagNode objects, typically produced by a call to the GetTags
method of the TTagNode class.
A Simple Example
HEAD
NodeType
Caption
HTML elements
Text
Comments
SGML/XML/DTD
directives
nteElement
ntePCData
nteComment
nteDTDItem
Element name
!
! or ? and directive
Each node has a NodeType property that indicates what type of node
it is. All node types except ntePCData also have text in the Caption
property that provides more information about the nodes contents.
See the table in Figure 3 for details.
For example, the HTML node in Figure 2 has a NodeType of
nteElement, while the comment tag is of type nteComment, and the
PCData nodes are of type ntePCData.
The content or text for each HTML element is contained in a node
in its Children list. For example, the TITLE node in Figure 2 has
a PCData node whose Text property contains Example HTML.
The GetPCData method of a TTagNode returns the PCData text for
all children of the node for which its called. Note, this method is
recursive and will return the PCData text for all nodes in the tree
beneath the node upon which its called.
Retrieving Elements
BODY
TITLE
PCDATA
Node Contents
Comment
H1
PCDATA
PCDATA
PCDATA
PCDATA
LI
PCDATA
PCDATA
ShowMessage(Elements.FindTagByIndex('li',1).GetPCData);
On the Net
were it included in Figure 4, would display the text for the second
occurrence of the <LI> element in the Elements container. This can
prove exceptionally useful for locating a specific tag from target
HTML content. For example, if the third <TABLE> in the HTML
contained the desired data, the following code would make quick
work of locating the root <TABLE> node:
HTMLParser1.GetTags('*',Elements);
Node := Elements.FindTagByIndex('table',2);
if Assigned(Node) then
begin
// Perform processing on <TABLE> node.
end;
results as a list. The goal here is to retrieve a list of all comments, links,
meta tags, and images from the document, with the <TITLE> thrown
in for good measure. The code does this in several simple steps:
1) Parse the HTML.
2) Create a container for the list of matching nodes from the tree.
3) Call the GetTags method passing '*', and the container ('*'
indicates that all items in the tree should be returned).
4) Iterate through the container collecting matches and place their
contents in the StringList.
5) Destroy the container.
The important thing to understand here is that the TTagNodeList
class is just a list of pointers to nodes from the Tree. This is quite
beneficial in that once a desired node is located in the list, it may be
used as if it had been acquired by traversing the tree. For example,
when the case statement in Figure 6 encounters a TITLE element,
On the Net
its contents are retrieved by making a call to the TITLE nodes
GetPCData method (which depends on the parsed tree structure
behaving as it appears to in Figure 2). Note that the PCData often
contains encoded items such as > and < (< and > respectively).
HTMLDecode is provided for handling most simple cases, but doesnt
handle all cases (notably non-US character encoding).
they contain images, not program content. Also, the channel appears
only in the first row of a set of programs that occur on that channel.
Further Study
This parser builds a reasonable basis for parsing of HTML and XML,
but offers significant room for further development.
On the Net
Further performance tuning. While some attention has been paid to this
area, no extreme efforts to speed things up were applied. Most notably,
Delphis string routines are not considered to be as fast as those provided
in some third-party string-handling collections (notably HyperString).
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD in INFORM\00\MAY\DI200005RP.
Richard Phillips is a Development Manager for i2 Technologies working on their TradeMatrix technology integrating disparate datasources. Hes been using Delphi since it was
introduced and Borland Pascal since 1985. He can be reached at richardp@dallas.net.
Figure 8: The Parser Test application.
On the Net
for DataCtr := 0 to RowNode.ChildCount - 1 do begin
Node := RowNode.Children[DataCtr];
if LowerCase(Node.Caption) = 'td' then
Table.AddColumnObject(Node.GetPCData,Node)
else
if LowerCase(Node.Caption) = 'th' then
Table.AddHeader(Node.GetPCData);
end;
if Table.Row[Table.RowCount - 1].Count <= 0 then
Table.DeleteRow(Table.RowCount - 1);
end;
end;
end;
end;
function TForm1.GetDescription(Node : TTagNode) : string;
var
TempStr : string;
begin
Result := '';
if Node.ChildCount > 0 then
TempStr := Node.Children[0].Params.Values['href']
else
TempStr := '';
if TempStr <> '' then begin
// Parse out the description id
Delete(TempStr,1,Pos('(',TempStr));
Delete(TempStr,Pos(')',TempStr),Length(TempStr));
WIGetURL1.URL := cDirecTVDescURL + TempStr;
Screen.Cursor := crHourglass;
Application.ProcessMessages;
WIGetURL1.GetURL;
Screen.Cursor := crDefault;
StatusBar1.Panels.Items[0].Text := '';
if WIGetURL1.Status = wiSuccess then begin
TempStr := WIGetURL1.Text;
// Use brute force to scrape out program description.
if Pos('<BLOCKQUOTE>',TempStr) > 0 then begin
Delete(TempStr,1,Pos('<BLOCKQUOTE>',TempStr) + 11);
Delete(TempStr,Pos('</BLOCKQUOTE>',TempStr),
Length(TempStr));
end;
Result := TempStr;
end;
end
end;
procedure TForm1.pbSearchClick(Sender: TObject);
const
tzPacific = '0';
// Time zones.
tzMountain = '1';
tzCentral = '2';
tzEastern = '3';
cgMovies = '0';
// Categories.
cgSports = '1';
cgSpecials = '2';
cgSeries = '3';
cgNews = '4';
cgShopping = '5';
cgAllCategories = '-1';
var
Cols,
Rows : Integer;
NewItem : TListItem;
Node : TTagNode;
Nodes : TTagNodeList;
ResultTable : TStringTable;
TempStr : string;
begin
if ebSearchText.Text = '' then
Exit;
WIGetURL1.URL := cDirecTVSearchURL + tzCentral + '/' +
cgAllCategories + '/' + urlEncode(ebSearchText.Text);
Screen.Cursor := crHourglass;
Application.ProcessMessages;
WIGetURL1.GetURL;
Screen.Cursor := crDefault;
StatusBar1.Panels.Items[0].Text := '';
if WIGetURL1.Status = wiSuccess then
begin
if Pos('No program titles that match',
WIGetURL1.Text) > 0 then
ShowMessage('No matches found')
else
begin
// Attempt to parse HTML table we're looking for.
HTMLParser1.Parse(WIGetURL1.Text);
Nodes := TTagNodeList.Create;
HTMLParser1.Tree.GetTags('table',Nodes);
if Nodes.Count > 0 then
begin
ResultTable := TStringTable.Create;
GetTable(Nodes[0],ResultTable);
// Get rid of image tags at bottom of search
// response (the 2nd column has no contents).
with ResultTable do
for Rows := RowCount - 1 downto 0 do
if Cells[1,Rows] = '' then
DeleteRow(Rows);
// Ensure all cells are filled appropriately
// (in the HTML table, a RowSpan attribute
// allows the "Channel" to be displayed in
// one cell for several programs).
with ResultTable do
for Rows := 0 to RowCount - 1 do
for Cols := 0 to ColCount - 1 do
if Cells[Cols,Rows] = '' then
if Rows > 0 then
Cells[Cols,Rows] :=
Cells[Cols,Rows - 1];
// Add items to ListView (Program, Channel,
// Data, Time).
ListView1.Items.Clear;
for Rows := 0 to
ResultTable.RowCount - 1 do begin
NewItem := ListView1.Items.Add;
NewItem.Caption :=
ResultTable.Cells[4,Rows];
NewItem.SubItems.Add(
ResultTable.Cells[0,Rows]);
NewItem.SubItems.Add(
ResultTable.Cells[1,Rows]);
NewItem.SubItems.Add(
ResultTable.Cells[2,Rows]);
end;
// Retrieve program descriptions (program id
// contained in 4th column's node).
for Rows := 0 to
ResultTable.RowCount - 1 do begin
// It's rude to whack the server :-)
Sleep(500);
Node :=
TTagNode(ResultTable.Objects[4,Rows]);
TempStr := GetDescription(Node);
ListView1.Items[Rows].
SubItems.Add(TempStr);
end;
ResultTable.Free;
end // if Nodes.Count > 0 ...
else
ShowMessage(
'Error - Expected table...found none');
end; // else of Pos('No program titles that...
end // WIGetURL1.Status = wiSuccess...
else
ShowMessage('Unable to contact search server [' +
WIGetURL1.ErrorMessage + ']');
end;
end.
Delphi at Work
Office 2000 / Add-ins / COM / Delphi 5
By Ron Loewy
Delphi at Work
// IDTExtensibility2 methods
procedure OnConnection(const Application: IDispatch;
ConnectMode: ext_ConnectMode; const AddInInst: IDispatch;
var custom: PSafeArray); safecall;
procedure OnDisconnection(RemoveMode: ext_DisconnectMode;
var custom: PSafeArray); safecall;
procedure OnAddInsUpdate(var custom: PSafeArray); safecall;
procedure OnStartupComplete(var custom: PSafeArray);
safecall;
procedure OnBeginShutdown(var custom: PSafeArray);
safecall;
To create the add-in, youll need to import some COM objects and
type libraries into Delphi. I used Delphi 5s TlibImp.exe (installed
in the /Bin sub-directory of the standard Delphi installation directory) to import everything. The new version of this utility supports
the new /L+ flag, which creates an OLE Server Delphi wrapper
around COM objects and automatically maps their properties and
events for easy Delphi use.
The IDTExtensibility2 interface that our add-in needs to implement
is declared in the file MSADDNDR.DLL, located in the \Program
Files\Common Files\Designer\ directory.
I used TLIBIMP /L+ \Program Files\Common
Files\Designer\MSADDNDR.DLL from the Imports
sub-directory of Delphis root directory. The result is the
file AddInDesignerObjects_TLB.pas (and
AddInDesignerObjects_TLB.dcr). We will need to use this file in
the uses clause of our project to gain access to the interface. For
some reason, TLIBIMP renamed the interface _IDTExtensibility2
(notice the underline prefix). Experience taught me to accept this
as a necessary evil. (I would suggest not fighting TLIBIMP for the
underlines that it loves to add at the start and end of interfaces,
properties, methods, or constants).
The next step is deciding which Office application (or applications)
we want to create the add-in for. This article will use Word 2000
as the sample. There are plenty of articles, books, and documentation resources about the different Office application object models.
Naturally, when you create an add-in for Outlook, Excel, or any
other Office application, youll need to access the object model for
the particular application and import its type libraries.
I imported Words type library from the file MSWORD9.OLB in
the \Program Files\Microsoft Office\Office directory. Similarly, if you
want to create an add-in for Excel, you will import EXCEL9.OLB;
for Access you need to import MSACC9.OLB; and for Outlook
MSOUTL9.OLB. TLIBIMP automatically imports the type library
of the shared Office components (MSO9.DLL). The result are the
files Office_TLB.pas and Word_TLB.pas.
Note that Borland supplied imports of the Office 97 files like
Word97.dcu. The unified add-ins architecture doesnt work in Office
97 applications, and we have to perform the new import to gain
access to the latest object models.
A Basic Add-in
Delphi at Work
The Office 2000 User Interface
A common set of objects that represents the user interface elements
is shared among all the Office applications. Menu bars, toolbars,
common controls (e.g. toolbar buttons and combo boxes), and even
the often-maligned Office assistant exist throughout the Office suite.
When you imported the Word type library earlier, the common Office
objects type library was also imported, and the Office_TLB.pas was
created. Unfortunately, when Delphi imports this type library, it doesnt
create OLE Server wrapper objects. We will have to manually create a
Delphi wrapper for the CommandBarButton object exposed by Office.
This object represents a simple menu item or toolbar button in Office.
Value Use
$0
$1
$2
$8
$16
A CommandBar object has a Controls collection, which is a collection of CommandBarControl items. A CommandBarControl is an
item that appears in a command bar; a simple CommandBarButton
is used to represent a simple toolbar button (or menu item), but
you can also create CommandBarCombo controls (combo boxes),
CommandBarPopup (drop-down menus), and CommandBarActiveX
(everything that wraps an ActiveX control).
Now we have a Delphi wrapper for a command bar button, and are
ready to interface with Word in our add-in. We need to declare a
TWordApplication field that will hold the reference to the Word applica15 May 2000 Delphi Informant Magazine
FWordApp : TWordApplication;
DICommandBar : CommandBar;
DIBtn : TButtonServer;
DIMenu : TButtonServer;
var
WA : Word_TLB._Application;
begin
FWordApp := TWordApplication.Create(nil);
WA := Application as Word_TLB._Application;
WordApp.ConnectTo(WA);
Delphi at Work
DICommandBar := nil;
for i := 1 to WordApp.CommandBars.Count do
if (WordApp.CommandBars.Item[i].Name =
'Delphi Informant') then
DICommandBar := WordApp.CommandBars.Item[i];
// See if we already registered the command bar with Word.
if (not Assigned(DICommandBar)) then begin
DICommandBar := WordApp.CommandBars.Add(
'Delphi Informant',EmptyParam,EmptyParam,EmptyParam);
DICommandBar.Set_Protection(msoBarNoCustomize)
end;
Notice that the first item in a collection in Office is item #1, not
item #0 as we are used to from the Windows API or Delphis TList
and TStringList. At this point, BtnIntf has the desired toolbar
button interface, and we can create a TButtonServer wrapper
around it:
DIBtn := TButtonServer.Create(nil);
DIBtn.ConnectTo(BtnIntf as _CommandBarButton);
DIBtn.Caption := 'Delphi Test';
DIBtn.Style := msoButtonCaption;
DIBtn.Visible := True;
DIBtn.OnClick := TestClick;
Delphi at Work
if (Assigned(DIBtn)) then begin
DIBtn.Free;
DIBtn := nil;
end;
if (Assigned(DIMenu)) then begin
DIMenu.Free;
DIMenu := nil;
end;
if (Assigned(DICommandBar)) then begin
// This is an interface, not an object!
DICommandBar.Delete;
DICommandBar := nil;
end;
Cleaning Up
Conclusion
Weve discussed the ease of creating add-ins for Office 2000 applications. The new COM-based add-in architecture makes it easy to share
add-in code among Office 2000 applications. The same COM object
can be used as an add-in for more than one application, and the
same user-interface objects can be shared among Office applications.
Also, if you create toolbars, menu items, or Office assistant Help
in one application, the same code will work in other applications.
For more information about Office development and the creation
of Office 2000 add-ins, consult the microsoft.public.officedev newsgroup hosted on msnews.microsoft.com.
The files referenced in this article are available on the Delphi
Informant Magazine Complete Works CD located in INFORM\00\
MAY\DI200005RL.
Ron Loewy is a software developer for HyperAct, Inc. He is the lead developer
of eAuthor Help, HyperActs HTML Help authoring tool. For more information
about HyperAct and eAuthor Help, contact HyperAct at (515) 987-2910 or visit
http://www.hyperact.com.
Dynamic Delphi
User Interface / Run-time Objects
By Ron Gray
Dynamic Forms
Creating Forms and Controls at Run Time
Possible Values
BorderIcons
BorderStyle
FormStyle
Height
Left
Position
Top
Width
WindowState
Figure 1: Properties of TForm that determine the type of window, its size, and position.
18 May 2000 Delphi Informant Magazine
This code is much better than the previous example because it clearly provides pertinent information about the form, and doesnt rely on guesswork about default properties. Of course, there are
many other properties that can be set to further
customize the form. As a general rule, all the properties a developer would set at design time must be
set programmatically at run time. Properties that
are rarely changed from their defaults should be
set programmatically as well, to promote clarity
and avoid surprises.
Dynamic Delphi
TControl, and the Owner property declared in TComponent. The
Parent is always a windowed control that visually contains the control or form. The Owner is passed as a parameter in the constructor
and determines when the component is freed. The former property
is used for display purposes, the latter for memory management.
Both are important.
TControl.Parent
Many forms do not have a Parent. For example, forms that appear
directly on the Windows desktop (like the dialog box in the previous
example) have Parent set to nil. However, a form can be embedded
in any other visual control by setting the Parent to that control. For
example, the same form that can be shown as a stand-alone form on
the desktop can also be embedded in a tab sheet by simply changing
the Parent property to the tab sheet.
For controls, the Parent is usually the form on which its displayed.
But it could also be a panel, group box, or some control that is
designed to contain another. When creating a new control, always
assign a Parent property value for the new control.
TComponent.Owner
There are a few properties of interest here. First, note that the buttons
position is relative to the form. The dimensions of the form can
change, but the button will still appear on the bottom right of the
form. When positioning controls relative to the form, use the client
area (ClientHeight and ClientWidth) rather than the actual form area
(Width and Height), which includes the forms caption and other
space that cant be used by the client. Panels and anchors are also
useful for positioning controls.
Second, if the form is shown modally, the ModalResult property can
be used to determine whether, and how, the button closes the form.
19 May 2000 Delphi Informant Magazine
Third, the controls Parent is the form, but it could have been a previously
created panel or other containing control. For example, a panel could be
created dynamically and anchored to the bottom of the form. The panel
would then be assigned to the buttons Parent so it will resize along with
the panel. Finally, the Show method is only required if the buttons Parent
(the form in the previous example) is already visible. Otherwise, when the
form is shown, it will cause all child controls to be shown as well.
A Simple Example
Calling this function displays a modal dialog box like the one shown
in Figure 2.
The function performs some simple math to determine the correct
widths of the buttons and form, based on the widths of the button captions and message. Once the form and button widths are determined,
the function simply loops through the array and creates a button for
each item. The buttons ModalResult is its one-based position in the
array, as shown in Figure 3. The Return value is the ordinal position of
the clicked button. Code can be written to respond to each button.
Figure 2: The MessageDlgEx function creates the form and buttons dynamically.
for ii := 0 to High(AButtons) do begin
btnButton := TButton.Create(frmForm);
btnButton.Height := 25;
btnButton.Width := nButtonWidth;
btnButton.Left :=
frmForm.Width - ((ii + 1) * (nButtonWidth + 10));
btnButton.Top := frmForm.ClientHeight - 35;
btnButton.Caption := AButtons[ii];
btnButton.ModalResult := ii + 1;
btnButton.Parent := frmForm;
end;
Figure 3: Once the form and button widths are determined, the
function simply loops through the array and creates a push button
for each item.
Dynamic Delphi
linked to pre-defined actions. No matter how generic and dynamic
a form is, it still needs a save procedure. The MessageDlgEx function
mentioned previously dynamically responds to events by simply assigning each buttons ModalResult property to its ordinal position in the
array. How the event is then handled is outside the functions control.
With more complex data entry forms, buttons must be linked to
specific actions, such as the OK button that calls a save routine, and
the Cancel button that performs any rollback procedures. Assigning
a specific action to a control requires code that responds to the
controls events, or overrides the controls message handler to respond
to messages. The easiest way is to respond to the controls events.
Delphi converts most Windows messages sent to the control to events.
At design time, developers link the controls events to methods of the
form. For example, the OK button, named btnOK, responds to the
OnClick event by calling the btnOKClick method of the form. Event
handlers can be assigned programmatically. However, the compiler
expects a method of a class rather than a stand-alone procedure, so the
method itself cannot be created programmatically. In other words, the
OK buttons OnClick event cannot simply call a generic SaveChanges
function, but rather must call a method of the form. An easy way
around this is to visually create a blank template form that is used
dynamically at run time, and write generic event handlers that can be
used by the various controls that are created programmatically.
For example, the OnClick event of an OK button must call the
SaveChanges function. The forms code defines a generic OnClick
event for all buttons. Within the method, specific actions can be
programmed for each button. So, if the OK button generated the
event, the SaveChanges function is called:
procedure TForm1.ButtonClick(Sender: TObject);
begin
if TControl(Sender).Name = 'btnOK' then
SaveChanges
else
ShowMessage('You did not click the OK button.');
end;
One of the more compelling reasons for creating forms and controls
dynamically is the ability to generate data entry screens at run time
that are either designed by the user, or based on some other data-driven
criteria. Creating data access components and data controls is no different
than what has already been described. Of course, the data controls must
20 May 2000 Delphi Informant Magazine
Dynamic Delphi
Of course, this is a simple data entry example, but the same techniques apply when creating more complex forms. For example, a selfconfiguring data entry form can simulate Delphis Form Wizard at
run time, automatically creating controls for each field in a selected
table. This requires looping through TDataSet.Fields, creating a control and associated label for each field in the data set (see Figure 6).
Note that the Caption of the associated TLabel control is set to the
field name. This is fine for small utility operations, but when using datadriven forms within an application, the applications framework should
provide a way to save and restore the DisplayName of each field. This
way, a more descriptive label is provided, rather than using the field
name.
Data validation is straightforward as well. Values can be validated
in the application before they are sent to the database server. For
field-level validation, use TField.EditMask to restrict data that can be
entered in the field, and TField.OnValidate to validate data before
the record is updated. To reject the current value of the field, raise
an exception in the event handler. For record-level validation, use
TDataSet.BeforePost, which is called just before posting the record.
Call Abort to cancel the Post operation.
Conclusion
Dynamic Delphi
oForm := TForm.Create(Application);
oForm.BorderStyle := bsDialog;
oForm.BorderIcons := oForm.BorderIcons - [biSystemMenu];
oForm.FormStyle := fsStayOnTop;
oForm.Height := 185;
oForm.Width := 450;
oForm.Position := poScreenCenter;
oForm.Caption := Caption;
// Loop through buttons to determine the longest caption.
nButtonWidth := 0;
for ii := 0 to High(AButtons) do
nButtonWidth := Max(nButtonWidth,
oForm.Canvas.TextWidth(AButtons[ii]));
// Add padding for the button's caption.
nButtonWidth := nButtonWidth + 10;
// Determine space required for all buttons.
nAllButtonsWidth := nButtonWidth * (High(AButtons) + 1);
// Each button has padding on each side.
nAllButtonsWidth := nAllButtonsWidth +
(10 * (High(AButtons) + 2));
// The form has to be at least as wide as the buttons.
if nAllButtonsWidth > oForm.Width then
oForm.Width := nAllButtonsWidth;
// Determine if the message can fit in the form's width,
// or if it must be word wrapped.
nCtrlHeight := oForm.Canvas.TextHeight('A') * 3;
nMessageWidth := oForm.Canvas.TextWidth(Msg);
// If the message can fit with just the width of the
// buttons, adjust the form width.
if nMessageWidth < nAllButtonsWidth then
oForm.Width := nAllButtonsWidth;
if nMessageWidth > oForm.ClientWidth then begin
// Determine how many lines are required.
nCtrlHeight := Trunc(nMessageWidth/oForm.ClientWidth);
// Add 3 more lines as padding.
nCtrlHeight := nCtrlHeight + 3;
// Convert to pixels.
nCtrlHeight :=
nCtrlHeight * oForm.Canvas.TextHeight('A');
end;
// Adjust the form's height accomodating the message,
// padding and the buttons.
oForm.Height :=
nCtrlHeight + (oForm.Canvas.TextHeight('A') * 4) + 22;
// Create the message control.
oLabel := TLabel.Create(oForm);
oLabel.AutoSize := False;
oLabel.Left := 10;
oLabel.Top := 10;
oLabel.Height := nCtrlHeight;
oLabel.Width := oForm.ClientWidth - 20;
oLabel.WordWrap := True;
oLabel.Caption := Msg;
oLabel.Parent := oForm;
// Create the pushbuttons.
for ii := 0 to High(AButtons) do begin
oButton := TButton.Create(oForm);
oButton.Height := 25;
oButton.Width := nButtonWidth;
oButton.Left :=
oForm.Width - ((ii + 1) * (nButtonWidth + 10));
oButton.Top := oForm.ClientHeight - 35;
oButton.Caption := AButtons[ii];
oButton.ModalResult := ii + 1;
oButton.Parent := oForm;
end;
Result := oForm.ShowModal;
end;
By Simon Murrell
Active Directories
Using ADSI on Your Windows NT and Windows 2000 Systems
ctive Directory is the directory service used in Windows NT 4.0 and Windows 2000.
Its also the foundation of Windows 2000. To access the Active Directory Service,
you need to use the API that Microsoft provides, named ADSI (Active Directory Service
Interfaces). ADSI is a set of COM interfaces used to access the different directory services.
Programmers can currently access four network directory structures using the providers
supplied in ADSI: WinNT (Microsoft SAM database), LDAP (Lightweight Directory Access
Protocol), NDS (NetWare Directory Service), and the NWCOMPAT (Novell NetWare 3.x).
ADSI now makes the Windows NT administrators job easier. ADSI allows the administrator to
perform common tasks, such as the addition of
new users, managing printers, security settings, and
controlling the NT domain. Because ADSI uses
COM interfaces, well-known languages, such as
Visual Basic, Visual C++, C++Builder, Delphi, or
any other COM-enabled language, can use ADSI
to write new client software. Well-known ISVs
have used ADSI to make their software applications directory-enabled.
Active Directory runs on either Windows NT
4.0 or Windows 2000. Client applications can
run on Windows 95, 98, NT 4.0, and 2000.
To use ADSI, you need to install the ADSI
COM interfaces. You can download the ADSI
2.5 SDK from the Microsoft ADSI Web site at
http://www.microsoft.com/adsi. The SDK contains documentation, online help, and samples.
The only problem is that the samples and documentation are aimed toward Microsoft products,
namely Visual Basic and Visual C++. This is one
of the main reasons Im writing this article: so
there are examples for the Delphi community.
The documentation is also mainly aimed at the
WinNT provider not the more common
LDAP provider used in Microsoft Exchange and
Microsoft Site Server.
The first parameter is the path name used to bind to the object
in the underlying directory service. The second parameter is the
interface identifier for a specified interface on this object; and the
third parameter is the indirect pointer to the requested interface.
By default, this function uses secure authentication, i.e. the function uses the security context of the current user.
The second function, ADsOpenObject, is used to bind to an ADSI
object using an alternate security context by stipulating a username
and password of the required user. The declaration of this function is:
function ADsOpenObject(lpszPathName: PWideChar;
lpszUserName: PWideChar; lpszPassword: PWideChar;
dwReserved: LongInt; const riid: TIID; out obj): HResult;
stdcall; external 'activeds.dll';
The first parameter is the path name used to bind to the object in
the underlying directory service. The second and third parameters
are the username and password of the user whose security credentials you want to use. The fourth parameter is a reserved provider
flag, which stipulates the authentication method you want to bind
with. The fifth parameter is the interface identifier for a specified
interface on this object. Finally, the sixth parameter is the indirect
pointer to the requested interface.
Once the ADSI object has been assigned, we check the child objects
class. According to the type of class, we then pass the ADSI to the
respective procedure to add the properties from the ADSI object to
the respective ListView component on the form:
The following is the interface variable of the children in the container object:
Once youve assigned all the values from the child objects, your
application should be filled with information, as in Figure 5.
// Procedure retrieves the domain information.
procedure TMainFrm.GetDomainInformation(
Domain: IADsContainer);
var
Enum: IEnumVariant;
ADsTempObj: OLEVariant;
ADsObj: IADs;
Value: LongWord;
begin
// Empty User, Group, and Computer lists.
UserListView.Items.Clear;
GroupListView.Items.Clear;
ComputerListView.Items.Clear;
// Assign enumerator object.
Enum := (Domain._NewEnum) as IEnumVariant;
// Search through enumerator object.
while (Enum.Next(1, ADsTempObj, Value) = S_OK) do begin
// Assign temporary object.
ADsObj := IUnknown(ADsTempObj) as IADs;
// If object is a user object then.
if AdsObj.Class_ = 'User' then
AddUserToList(ADsObj);
// If object is a group object then.
if AdsObj.Class_ = 'Group' then
AddGroupToList(ADsObj);
// If object is a computer object then.
if AdsObj.Class_ = 'Computer' then
AddComputerToList(ADsObj);
end;
end;
ADsObj: IADs;
With the WinNT provider, one can create and remove users from any
computer within any domain. To create a user within the domain on
a specified computer, you need to bind to the computer to which you
want to add the user. Once youve bound to the ADSI container object
of the required computer, you need to call the Create method. The Create
method of the container object takes two arguments. One is the type of
ADSI object you want to create; the other is the name describing the
new ADSI object. The Create method then returns a reference to the new
ADSI object created. Figure 6 is the source code that creates the user.
As usual, first the variables are declared. The first is the computer
container variable to bind to. From this container, well add the new
user to the domain:
ComputerObj: IADsContainer;
var
ComputerObj: IADsContainer;
TempUserObj: IUnknown;
UserObj: IADsUser;
PDCName: WideString;
NewUserName: WideString;
AdsPath: WideString;
begin
// Retrieve information from user.
PDCName := InputBox('Create New User',
'Please type in the name of the Domain's PDC : ', '');
NewUserName := InputBox('Create New User',
'Please type in the user name : ', '');
// Assign AdsPath.
AdsPath := 'WinNT:// + PDCName + ',computer';
// Create computer object.
OleCheck(AdsGetObject(PWideChar(AdsPath),
IID_IADsContainer, ComputerObj));
// Create new user.
TempUserObj := ComputerObj.Create('user', NewUserName);
UserObj := TempUserObj as IADsUser;
// Set information back to directory.
UserObj.SetInfo;
// Refresh list.
actOpenWinNT.Execute;
This WideString variable will be used to store the name of the PDC
(Primary Domain Controller) computer of the domain. You can specify
any other computer name, and the source code will add the user to the
computer, but the source code will only return the users from the domain:
We then call the Create method from the ADSI container object,
which will create the new user and return the new user object back to
the temporary ADSI object. We then assign the temporary interface
object to the ADSI user object:
PDCName: WideString;
NewUserName: WideString;
Here, we assign the ADSI object path of the computer that we want
to bind to:
// Assign AdsPath.
AdsPath := 'WinNT://' + PDCName + ',computer';
With the WinNT provider, you can also control the NT groups by
adding and removing users from the groups, and performing other
group maintenance tasks. In the example application, Ive decided to
enumerate the group and view the users found within the groups in
the specified domain. The code in Figure 8 binds to an IADsGroup
GroupObj: IADsGroup;
var
ComputerObj: IADsContainer;
PDCName: WideString;
UserName: WideString;
AdsPath: WideString;
begin
// Retrieve information from user.
PDCName := InputBox('Create New User',
'Please type in the name of the Domain's PDC : ', '');
UserName := InputBox('Create New User',
'Please type in the user name to delete : ', '');
if MessageDlg('Are you sure you want to delete user : ' +
UserName + ' ?', mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
begin
// Assign AdsPath.
AdsPath := 'WinNT:// + PDCName + ',computer';
// Create computer object.
OleCheck(AdsGetObject(PWideChar(AdsPath),
IID_IADsContainer, ComputerObj));
// Create new user.
ComputerObj.Delete('user', UserName);
// Refresh list.
actOpenWinNT.Execute;
end;
AdsPath: WideString;
We assign the ADSI object path of the group we want to bind to:
// Assign AdsPath.
AdsPath := 'WinNT://' + MainFrm.ADSIDomainName.Text +
'/' + GroupName;
We then create the new IADsGroup object from the specified ADSI
object path:
// Create group object.
OLECheck(AdsGetObject(PWideChar(AdsPath),
IID_IADsGroup, GroupObj));
Then we search through the list of NT users, and assign each member
to the temporary interface variable:
// Search through enumerator object.
while (Enum.Next(1, TempUserObj, Value) = S_OK) do
Next, we assign the temporary interface variable to the user object. Then
we create the list item and assign the users username to the list item:
// Assign temporary object.
UserObj := IUnknown(TempUserObj) as IADsUser;
// Create new list item.
TempListObj := GroupListView.Items.Add;
// Assign property.
TempListObj.Caption := UserObj.Name;
Controlling NT Services
For example:
var
UnknownObject: IUnknown;
Computer: IADsContainer;
ComputerPath: WideString;
Enum: IEnumVariant;
AdsTempObj: OLEVariant;
AdsObj: IADs;
Value: LongWord;
begin
if Item.Caption = '' then
Exit;
// Assign computer path.
ComputerPath := 'WinNT:// + ADSIDomainName.Text +
'/' + Item.Caption;
// Create computer object.
OleCheck(ADsGetObject(PWideChar(ComputerPath),
IID_IADsComputer, UnknownObject));
// Assign computer object.
Computer := UnknownObject as IADsContainer;
// Remove items from list.
ServiceListView.Items.Clear;
// Assign enumerator object.
Enum := (Computer._NewEnum) as IEnumVariant;
// Search through enumerator object.
while (Enum.Next(1, ADsTempObj, Value) = S_OK) do begin
// Assign temporary object.
ADsObj := IUnknown(ADsTempObj) as IADs;
// If object is a service object then.
if AdsObj.Class_ = 'Service' then
AddServiceToList(ADsObj);
end;
Then we create the new IADsService object from the specified ADSI
object path:
Figure 10: This code searches for NT services and assigns the
services to the respective service list according to the computer
selected in the computer list.
We assign the ADSI object path of the service we want to bind to:
var
ServiceObj: IADsService;
AdsPath: WideString;
begin
// Assign AdsPath.
AdsPath := 'WinNT://' + ComputerName + '/' + ServiceName;
// Assign Service Object.
OLECheck(ADsGetObject(PWideChar(AdsPath),
IID_IADsService, ServiceObj));
// Assign labels.
lblServiceName.Caption :=
'Service Name : ' + ServiceName;
lblDisplayName.Caption := 'Service Display Name : ' +
ServiceObj.Get_DisplayName;
Conclusion
Greater Delphi
VisiBroker / CORBA / IDL2PAS
By Eric Whipple
know what youre thinking: Does the assembly method of development ever actually
work, or is it just something I told the vice president to make him feel better about
the value of distributed systems? The assembly method, of course, refers to the creation
of software by gluing together pre-made pieces of functionality to create a total
development solution. By taking advantage of the flexibility and power of interface-based
development, the CORBA (Common Object Request Broker Architecture) standard has
brought us one step closer to this development dream.
The question so many Delphi developers struggle
with is: Can Delphi hack it in a CORBA world?
The answer is, Yes. VisiBroker 3.3 for Delphi
firmly establishes Delphi as a major tool for creating powerful, Windows-based CORBA clients.
Greater Delphi
municate with their CORBA servers, but has not developed latestgeneration ATM client software because many of its older subsidiaries cant purchase or run the applications. Its your responsibility
to create a quick and easy client application to connect to the First
American server.
In the past, First Americans corporate inertia would have
caused a serious problem for your Delphi developers. Because
Delphi had no way to create client code based on existing interface declarations, you wouldve been forced to develop
your Delphi CORBA clients from scratch by hand. This
includes the creation of marshalling and stub code for every
implementation object, which is tedious to say the least. Even if
your developers are intimately familiar with Delphi and CORBA,
the cost of writing the application (in developer hours) alone
could prohibit you from realistically attempting it (not to
mention debugging it).
Greater Delphi
a generic language, it doesnt matter what language the server was
written in. Lets see how this new tool affects our bank example.
We start by executing the IDL2PAS command on BankServer.Idl by
opening a DOS window and typing the following:
IDL2PAS BankServer.Idl
Figure 4: Then, we can begin making calls to the servers IAccountManager interface.
Greater Delphi
objects and other constructs contained in an IDL file. An interface
repository contains information about the ORB and any objects
with which it is currently communicating. This can be very useful
for dynamic binding.
Building a Client
The downloadable code for this article (see end of article for details)
contains a CORBA server and a traditional client, as well as an
IdlClient application. Lets take a look at how to use the xxx_i.pas
and xxx_c.pas files. The client application consists of a simple
form thats used to call methods on the server. After running
IDL2PAS on BankServer.Idl, the resulting BankServer_i.pas and
BankServer_c.pas files are used by the main form of the client application (notice that we need nothing from the server once we have
the IDL file). Once weve called CorbaInitialize, all that remains is
to bind to a factory object and request an IAccountManager object
(see Figure 3). Once thats done, we can begin making calls to the
servers IAccountManager interface (see Figure 4).
Comparing the two client programs, we see there isnt much difference in terms of time or complexity. The difference, of course, is
that if the server had not been written in Delphi, the IdlClient
would still work as-is, while the standard client would have
required a good bit of work.
The first version of any tool always includes some known issues.
It would be misleading to say that VisiBroker 3.3 for Delphi is
any different. Two of the most notable issues include the fact that
Conclusion
Eric Whipple is a Delphi trainer and mentor for Pillar Technology Group, Inc.
of Detroit, a full-service consulting, training, and mentoring firm specializing in
project management and in the analysis, design, and development of distributed,
enterprise systems (http://www.knowledgeable.com). Eric is a Delphi 4-certified
developer and trainer, and can be reached at ewhipple@knowledgeable.com or
by phone at (317) 915-9031.
Best Practices
Directions / Commentary
o you comment your code? Commenting is one of those topics upon which you can hear conflicting opinions from
various programming experts pseudo, self-proclaimed, or legitimate. Some say you should always comment
your code. Others say, basically, that commenting is for ninnies and newbies; its unnecessary for real programmers,
because you should be able to read the code and determine what its doing.
In the classic book Code Complete by Steve McConnell (Microsoft
Press, 1993), Socrates is quoted as saying, I think that people who
refuse to write comments 1) think their code is clearer than it could
possibly be; 2) think that other programmers are far more interested
in their code than they really are; 3) think other programmers are
smarter than they really are; 4) are lazy; or 5) are afraid someone else
might figure out how their code works.
After maintaining many other peoples code on several different
projects, I agree with Socrates and am firmly entrenched in the
commenting-is-a-good-thing camp. But not just any kind of commenting. Many reluctant commenters end up producing worthless
comments, thus completing a self-fulfilling-prophecy.
Those who object to commenting often say that: 1) they dont have
time for commenting; or 2) all you need to do is read the code to
see what its doing.
Let me respond to these objections in turn. First, in regard to having
no time for commenting (Im too busy doing the real work of
coding): If you dont have time for commenting your code at the
time you write it, when will you have time for it? Using PDL
(programming design language; see Code Complete for details and
examples) to delineate what a section of code should do, and adjusting and/or enhancing it immediately following coding, when the
logic (and the reason for it) is still fresh in your mind, is a coding
best practice. As in analysis and design, this work done up front
will, in the long run, save you time.
As to comments being unnecessary because the code explains itself:
The problem here is a failure to understand the true nature and
purpose of commenting. Its true that low-level comments (at least, if
the code has been written as cleanly and simply as possible) shouldnt
be necessary. What comments should convey is a high-level view of
the code. In laymans terms: what does it do? why? when is it called
and by whom? and how does it fit in with the overall body of code?
Viewed in this light, commenting can be likened to news reporting:
Be sure to convey who, what, why, where, when, and how. Done
right, commenting is an integral part of the coders responsibility (to
the coder, maintenance programmers who will have to work on the
code, and the coders employer or client).
Not all comments are good comments, however. For example, this
type of comment:
// Loop through the doohickeys.
for i := 0 to Doohickeys.Count-1 do
sl.Add(Doohickey[i]);
is superfluous. Its obvious from looking at the code that its looping
through the doohickeys. Any programmer can see that.
Here, on the other hand, is a good example of commenting. The
reader is provided with information thats neither transparent nor
trivial:
function AddLeading0s(ADate: string): string;
begin
// If "m/", make it "mm/".
if (Pos('/',ADate)=2) then begin
ADate := '0'+ADate;
Result := ADate;
end;
// If now "mm/d/", make it "mm/dd/".
if (ADate[5] ='/') then
Result := Copy(ADate,1,3)+'0'+Copy(ADate,4,4);
end;
Best Practices
{ ************************************************************** }
{
}
{ BSaleObj (unit)
}
{ No variable/instance name. Aggregation used in dm (this
}
{ object is encapsulated in that one as a private field).
}
{
}
{ This is the custom report object for the "Billed Sales
}
{ Report by Sales Rep" and "Billed Sales Credit Report by
}
{ Sales Rep" report. These are two quite dissimilar reports, }
{ but happen to be accessed from the same interface.
}
{
}
{ See NBFCW017
}
{
}
{ "BILLED SALES REPORT BY SALES REP" portion:
}
{ "Order Number" is extracted from Arledger.OrderNum and
}
{
ARLedger.VendorCode
}
{
}
{
"Bill-To" is extracted from Customer.Company based on
}
{
ARLedger.CustNum
}
{
}
{
"Margin" is calculated - (CustOrder - InvoicedCost)
}
{
}
{
"Profit %" is calculated }
{
(((CustOrder-Cost) / CustOrder)*100)
}
{
}
{
. . . additional notes left out for brevity
}
{
}
{ Coded by Rupert "Ruprecht" Pupkin III
}
{
}
{ Revision History:
}
{
05/27/1999
Began coding:
}
{
09/28/1999. It has been decided that the "Cost X Factor" }
{
and "Cost" columns are not needed. Rather than code them }
{
out, it will be faster to simply suppress them from
}
{
printing. A new column "Sell Price"*, will take the place }
{
(in the string list) of "Cost".
}
{
* Cost * Quantity.
}
{
}
{ ************************************************************ }
implementation; what other units use this one; how the various
methods are used, etc.
You can make it easier on yourself to add unit and method comment
headers if you create templates for them. You can do this by following
these steps:
1)
2)
3)
4)
5)
6)
{
Name of unit
Purpose of unit
Anything unusual
Coded by:
Revision history:
Began coding
}
7)
8)
Clay Shannon
Clay Shannon is a Delphi developer for eMake Corporation, located in Post
Falls, Idaho. Having visited 49 states (all but Hawaii) and lived in seven, he
and his family have settled in northern Idaho, near beautiful Coeur d Alene
Lake. He has been working (almost) exclusively with Delphi since its release,
and is the author of the book Developers Guide to Delphi Troubleshooting
(Wordware, 1999). You can reach Clay at clayshannon@usa.net.
The Future of Computing: Preparing for Delphi for Linux (cont. from page 36)
writers I enjoy reading as much as Swan, so I recommend this
book highly.
The last book is probably the most unusual. Maximum Linux Security: A Hackers Guide to Protecting Your Linux Server and Workstation
(SAMS, 1999) lists no author. Whats going on here? Is the content
so controversial that the author must disguise his or her identity? Perhaps. Its well known that some of the most successful security experts
have had experience with hacking into seemingly secure networks.
This work is particularly well researched, with a wealth of specific
examples of security setups that failed.
The opening section provides basic information on Linux security,
including the role of the system administrator. The second section
concentrates on the Linux user and deals with password and code
(virus) issues. The third section explains techniques that apply to
networks, while the final and longest section covers the Internet.
Because Linux is particularly popular in running network servers,
35 May 2000 Delphi Informant Magazine
File | New
Directions / Commentary
ast month, we explored some of the issues related to the eagerly anticipated Delphi for Linux. This month, well
continue that discussion and take a look at five books that might be helpful in preparing for this momentous
event. First, Id like to share a message I received from a reader regarding my January 2000 column on Delphi
and Visual Basic.
[In your article] I think you left out one of the most important
points for using Delphi: it will be ported to Linux. Microsoft (MS)
will never do this. Therefore as the world evolves away from MSs
buggy software there will be no MS competition. Once Inprise ports
[Delphi to Linux] there will be Corel [WordPerfect] Suite on the
desktop with Paradox as a desktop database and Delphi as your
enterprise solution. Where will MS be? Who knows and who cares.
Some people say MS will never loose the desktop. Once consultants
realize they can pocket 50% of the money that MS charges for their
OS and still leave the client with a surplus it wont take long for the
massive defection.
Interestingly, I received this the day after I submitted my previous
column. Will these predictions come to pass? Who can say? However,
it is clear that Linux will provide increasing competition for Microsoft Windows in the coming years. Windows and Linux do have one
interesting thing in common: Most of the high-end programming
for each is written in C. So in this column, I will discuss both the
general books introducing Linux and books about programming for
Linux using C.
In writing last months column, I found one book particularly
helpful in developing a general understanding of Linux: Caldera
OpenLinux 2.3 Unleashed by David Skoll (SAMS, 1999). If youre
looking for an excellent introduction to Linux, this is worth
examining. The first part covers all the basic information about
Linux, from the historical information I included in my previous
column, to working with the Linux shell, to Linux text editors. It
also includes an introduction to the X Windows system, Linuxs
graphical user interface. Many developers will be interested in
using Linux on a server. For them, the second section dealing with
System Administration will be of particular interest. This work
covers all the basic tasks, including working with file systems,
printing, setting up a TCP/IP network, and configuring a system,
among many others.
However, Linux Programming Unleashed by Kurt Wall, et al.
(SAMS, 1999) is written specifically for developers and deals with
advanced programming topics such as input/output, memory
management, and communication. Not surprisingly, the emphasis
is on C/C++; however, there are sections on Java and other languages. The first section of the book provides directions for set36 May 2000 Delphi Informant Magazine