Dr. Dobb's Journal April 1997
Al is a DDJ contributing editor. He can be contacted at 71101.1262@compuserve.com.Language mutates. Time was, if someone did you a good turn, you'd say "Thank you," and they'd say "You're welcome." The sentiment represented by "you're welcome" was a simple one. It told you that you are welcome to the favor, service, gift, whatever, no matter the inconvenience. Even when the inconvenience was slight or nonexistent, the thought was still there. And you were assured that even if the benefit had been a discomfort in the backside to render, you'd still be welcome to it. That was a nice tradition.
Listen up. Check it out. Get real. The language of the '90s reflects subtle changes in our culture. We are less cordial and more abrupt. "You're welcome" has been replaced. Now, in response to your offer of thanks, you are told curtly, "No problem," which sends a completely different message. Instead of reassuring you that you are welcome to any small inconvenience the favor might have entailed, the idiom reports that there was no inconvenience whatsoever, and furthermore, if there had been, you might assume that the deed probably would not have been so willingly proffered. Pity. This is for Miss Manners to consider, and I leave it to her to mandate proper usage for polite society (and to them to heed her counsel). The rest of us just have to make do.
Programming language mutates, too. However, while we might drift unconsciously into new verbal language idioms, we cherish and guard tradition in programming language. We do not change willingly and must be dragged kicking and screaming all the way. For example, why do assembly-language programmers hate Cobol? Many of us were forced to take that quantum leap, and maybe that's the origin of our distrust of new programming languages. On the other hand, most assembly-language programmers love C at first sight for obvious reasons -- its benefits are self-evident. It takes, however, a compelling reason to look beyond C. More kicking, more screaming.
I've said before that I credit the abstruse Windows API and its event-driven, message-based programming model for the overwhelming success of C++. Developers for other platforms might disagree, but I humbly submit that C++ would not have the commanding following it now enjoys without the Windows platform to compel it. Windows' popularity with users and their consequential demand for applications created a need for easier and better ways for developers to get around that API. GUI frameworks and C++ classes are a natural marriage.
My conversion to C++ predated my induction into Windows programming. As the author of this column, I try to keep pace with what you want to read about, and you wanted to read about C++. That is how the "C Programming" column several years ago changed its emphasis to C++. If your language of choice had been Objective C or C+@ or some other object-oriented C dialect, that's where we'd be. I eventually learned, as many others have, that C++ is a better C and that its object-oriented extensions support a superior programming model. But, before learning that lesson the hard way, I was skeptical. All over the world, programmers were asking the same question. Given that we are successfully writing programs using C, what compels us to change? The answer, of course, is that C++ is a better solution to what we were doing. But like Scotch whiskey, olives, and anchovies (not necessarily in the same recipe), C++ is an acquired taste. Learning C++ is a process of discovery. You must discover for yourself its advantages, which, unlike those of C, are not self-evident. That discovery comes only from experience, not from books, magazine columns, or listening to the ravings of evangelists.
Now I ask a similar question. Given that I am successfully writing programs with C++, why should I use anything else? Why should I write about anything else? This is, after all, the "C Programming" column, and C++ is a dialect of C, which makes it fair grist for my mill. But what about other dialects?
Another dialect of C has caught my attention, not as a replacement for C++, but as the best way to solve a particular problem. Necessity is, after all, the most compelling motivation. That dialect is JavaScript. Please ignore that muffled kicking and screaming in the background.
Last year I published in this column a program named "MidiFitz," an application that uses the Windows 95 Multimedia MIDI API to generate a real-time rhythm section for a keyboard player. (Which raises another linguistic issue. Inasmuch as "media" is a plural noun, isn't "multimedia" redundant? But, I digress.) MidiFitz was of interest to programmers who could learn about the API from the program, which is why I published the source code. Its purpose, however, was personal. I intended MidiFitz only for me to practice jazz piano at home and was unprepared for the response I got from my keyboard musician friends, who all wanted one. Over the months, their requests for changes, and mine, too, caused me to add features and write a new version, which I implemented as a property-page dialog application. I've written in this column about the technical issues involved in that exercise.
At the urging of my friends, who thought that MidiFitz should be more widely available, I embarked on an experiment in something relatively new -- web marketing. I decided to release MidiFitz as a commercial product marketed exclusively on the World Wide Web. I am not as interested in becoming an applications software mogul as I am in learning and writing about the Web as a vehicle for budget-conscious programmers to bring their vertical applications to market. The only alternative is shareware, a great idea that works well for horizontal applications but not so well for applications with less general appeal.
As a niche product, MidiFitz seems ideal for the web arena. The potential marketplace is limited, way too small to justify traditional print or broadcast advertising media. But the potential users are MIDI keyboard players, most of whom are not readers of programming magazines but who are, nonetheless, computer literate and dedicated web surfers.
Advertising on the Web means having a home page, which means building one with HTML. I won't try to explain HTML here except to say that it is a protocol for describing the architecture of documents that web browsers display. Unless you recently emerged from a two-year cloister, you already know that HTML is ASCII document content with embedded ASCII codes, called "tags," that define the architecture of the document. The browser uses the tags to render the document's content on the screen and interact with the user. I recommend Web Publishing with HTML 3.2, by Laura Lemay (Sams.net Publishing, 1996), as a good and comprehensive book about HTML.
Contemporary browsers support lots of neat stuff that you can embed in HTML code with the tag mechanism. Potential buyers can download a demo of your application by clicking on a link. They can listen to a MIDI or WAV file playback. They can view AVI files and animated GIF graphics. Best of all, they can order the product online.
This one feature -- online ordering -- according to neo-techno marketing specialists, makes the Web the near-perfect forum in which to promote a product because of the way that potential buyers come to your page. Buyers view a particular web site because they choose to navigate to it, either from a search engine that keys on words and phrases that interest them or from a link on a related web site that they have specifically chosen to view. They come to your advertising page on purpose -- not as a coincidental side effect of reading a magazine or watching TV, but of their own accord in search of things that address their interests. Talk about your targeted audience. Once you have their attention, if you make it easy for compulsive, er, decisive buyers to place an order, the paradigm is complete. They fill in the fields on an online data entry form, click a submit button, and the product is in the mail. That's the theory, anyway. Build it, and they will come.
An order form involves many data-entry fields; some of them depend on the data values that the buyer enters in other fields. Total price is the product of quantity and unit price. Shipping and handling (S/H) costs are a function of how fast the buyer wants the product and whether the buyer is overseas. Sales tax depends on whether the buyer lives in the same state as the seller (and if that state has a sales tax, of course). If you build a dumb order form that depends on buyers to get all those details right and compute a correct total cost themselves, a significant percentage of orders will arrive with something wrong. Better to provide an on-line form with the intelligence to validate the entries and calculate the totals. One way to add that intelligence is to embed JavaScript functions in your HTML code.
JavaScript, a semi-subset of Java, is a typeless language similar to traditional Basic and not quite as object oriented as Visual Basic, which is hardly at all. You embed JavaScript functions in an HTML page definition file and associate user events on the page's input components with those functions. Listing One contains the JavaScript functions from the MidiFitz order entry page. To view the full HTML, surf to http://www.midifitz.com/order.html and use your browser's View/Source command.
Each data entry field in an HTML page definition is represented by an INPUT tag with a type attribute that defines the entry type (text, radio, checkbox), its length, its identifier, and so on. Text fields may also include an attribute named onChange, which specifies a JavaScript function to be executed when the client changes the value of the field. Checkbox and radio button input fields may include an onClick attribute. When users click the control on the page, the browser executes the JavaScript function.
Consider the accumPrice function in Listing One. When the user changes any field that would affect the price, the HTML INPUT statement's onChange attribute tells the browser to execute the accumPrice function. The function ensures that the quantity field has a value of at least one. Then it computes the price by multiplying the quantity times the unit price. (The padString function, called by this and other functions, compensates for a floating-point multiplication precision bug in Netscape Navigator's JavaScript interpreter.) The accumPrice function sets the S/H value based on the S/H option chosen by the user and adds sales tax if the user's address specifies Florida (the home of MidiFitz) as the state. All the computed values are written to the form. If the user tries to override a computed value, the accumPrice function corrects the error.
A special HTML INPUT type attribute is called SUBMIT, and it creates a Submit button on the form. The FORM tag, which defines the start of the data entry portion of a page, includes an onSubmit attribute, which specifies a JavaScript function to execute when the buyer clicks the Submit button. I use the onSubmit attribute to call the validateData function, which ensures that all the required fields are filled in. If the function returns false, nothing happens. If the function returns true, the browser does whatever the FORM tag's ACTION attribute specifies. The ACTION attribute provides a URL for the browser to pass to the server as the next place to go, in this case to submit the order.
The URL in the ACTION attribute can be the name of an executable program in the server. Such a program is called a Common Gateway Interface (CGI) script. CGI programs are called "scripts" because many of them are implemented in interpreted language source code. But even when a CGI program is a compiled executable, they call it a script.
Getting the buyer's order-form data to the seller involves extracting the data from the form and sending them to the seller in an e-mail message. This process is one of the things that a script does.
Writing a CGI script is a lot easier than it looks. The browser and the server do most of the hard work.
The server executes the CGI script program when the browser passes the program file's URL to the server as a result of a user action. A CGI script's URL contains the path and name of the script file on the server or an alias that the server can translate. The server uses the path specification to recognize that the URL is a CGI script specification and the filename extension to determine what kind of CGI script to run. These details might vary depending on which server software is running. I'm describing the environment of the Web site that hosts MidiFitz, a UNIX system with the Apache server system.
Perl is a popular CGI interpreted language. You can write a CGI script in any language that reads and writes the standard input/output devices and that can read the values of environment variables, all of which you can do with Perl, Tcl, C, and C++.
Some things to know about CGI scripts:
To learn CGI programming, and to learn specifically how to make a CGI script create an e-mail message with data from the form's INPUT fields, I had to learn something about Perl. My web site provides a generic Perl script that does some of what I want to do, and I had to read that source code to learn how it worked. I found a small book named Introduction to CGI/Perl, by Steven Brenner and Edwin Aoki (M&T Books, 1996), which taught me how to read Perl. You have to love a programming language where this is a valid (if not typical) line of source code:
$s=~s/.*\.//;
Don't ask me what that means.
The server passes data-entry field values from the form to the CGI script in one of two ways depending on whether the FORM tag's METHOD attribute is GET or POST. A GET form passes the data values as one long string assigned to an environment variable. A POST form passes the data values as one line of text on the standard input device. Both methods use the same format for the data string. Each INPUT field is represented by its NAME attribute, an equal sign, the data value, and a question mark field separator. The last field does not have the terminating field separator. Spaces in the data value are replaced by plus signs. Other characters, reserved as control characters, are replaced by escape sequences consisting of a percent sign (%) and a two-digit hexadecimal value.
Recall that a CGI script communicates with the server by writing HTTP commands to the standard output device. HTTP is the language that servers and browsers use to communicate. The first line of text tells the server the nature of the communication. The CGI script must tell the server where to send the browser when the script is finished running. This can be a location command, which specifies the URL of a page, or it can be the complete HTML text for a dynamic page to display. An order entry submittal CGI script should give the buyer some message that says that the order was submitted; the best way to do that is to display a page that says so. If the confirmation page needs to display data based on the order's details, the CGI script creates that form on the fly. Otherwise, the CGI script can provide a new page URL for the server to display on the browser.
In most cases, you must get your web administrator's permission to install CGI scripts. Be aware that some web service providers charge an extra monthly fee if your web page uses CGI scripts. You can test much of a CGI script off-line on your PC by using input/output redirection to simulate the dialog between the script and the server, but most testing has to be done with the script installed and compiled on the web site. I use Quincy 96 to test scripts off line. Quincy 96 is the Windows 95 IDE front end for the gnu-win32 port of the GNU C/C++ compilers. I published the details of Quincy 96 in this column last year. My CGI script program is compiled by a GNU compiler at the web site, so Quincy 96 provides a measure of cross-platform compatibility.
Some web administrators want to look at your source code to ensure that your script does not unintentionally allow the user's input on the form find its way onto the command line of a UNIX program that your script executes. Don't be surprised, however, if the administrator is a Perl programmer who does not read C or C++.
Listing Two (order_proc.c) is the CGI script that processes MidiFitz orders. I used C instead of C++ because the administrator has not yet installed the GNU C++ compiler on the server. The version of GNU C is an old one, so I had to substitute the C comment tokens in place of the C++ comment tokens, which more current GNU C compilers recognize.
I did not try to make this program a generic input-forms processor. Such programs exist and are readily available, and they usually hide the details of the HTTP dialog in libraries. Instead, the order_proc script specifically supports the requirements of MidiFitz order entry, which are, I think, typical. Later, when I can, I'll port the program to C++, and everything will be properly wrapped in classes.
The program includes a set of functions with identifiers that start with the Page prefix. These functions encapsulate the small subset of the HTML output that the script generates to create an order confirmation page.
The main function reads one line of stdin text to get the input-field data values. Here's an example of how that looks. If a form has three input fields named, "name," "address," and "phone," and the user types "Judy Stevens," "123 Center Street," and "1234567" into the three fields, the line of text on standard input will look like Example 1(a). The main function scans this string and replaces the plus signs with spaces and the ampersands with newlines so that the string now looks like Example 1(b).
The program forms this string into an e-mail message by using the UNIX popen function to create a fork and execute the UNIX sendmail program. The "w" argument to the popen function says that popen should return the FILE* handle to sendmail's standard input device. order_ proc can write to this handle to create an e-mail message to send. The e-mail message includes strings to identify the sender, which is notational only; the recipient, which is the e-mail address where orders are to be sent; a notational subject, which you can use to distinguish orders from other e-mail when the message arrives; and the text of the message, which is the converted string of text built from the order_proc script's stdin, and which contains the data entry fields filled in by the buyer. Whoever monitors that e-mail address picks up the message and fills the order.
After completing this program, I found a book titled CGI Programming in C & Perl, by Thomas Boutell (Addison-Wesley Developers Press, 1996), which explained all of what I had to figure out on my own and also added some new information about CGI programming. It's a good CGI tutorial for C programmers. Don't expect it to teach you C or Perl. The examples assume that you already know the languages. One complaint: Publishers who allow source code to wrap on the printed page should find other work. The author should have caught and corrected these errors, which permeate the book. Don't type in any of the code without looking at it very carefully.
After you get into CGI programming, you might want to look into FastCGI, a protocol that extends the CGI protocol. FastCGI programs run more efficiently than traditional CGI programs. The server launches a new copy of a traditional CGI program for each request from the browser. FastCGI programs run as threads that share one copy of the program for multiple invocations. You can learn about FastCGI at http://www.fastcgi.com/. You can also download a free C library that implements a C API to the FastCGI protocol. (It's my understanding that DDJ will be publishing an article on FastCGI within the next month or two.)
I've thrown a lot at you with these explanations, which would benefit from more structured examples. CGI Programming in C & Perl, given the room allocated for a book rather than a column, does a better job of showing how CGI works, taking it step by step. There are some good books on JavaScript, too. JavaScript and CGI programming are a lot easier than it seems they ought to be given the arcane nature of most contemporary APIs. Someone went out of their way to make these interfaces easy and intuitive. We are lucky, I suppose, that these two great ideas did not originate in Redmond.
Perhaps the examples explained in this column will help you with your own web-publishing development. If so, well...no problem.
DDJ
<SCRIPT LANGUAGE="JavaScript"><!---------------------------------------------------------------
function isOK(field)
{
if (field.value == null || field.value == "") {
alert("Please enter " + field.name);
field.focus();
field.select();
return false;
}
return true;
}
function validateData(orderform)
{
accumPrice(orderform);
if (isOK(orderform.name))
if (isOK(orderform.address))
if (isOK(orderform.account_number))
if (isOK(orderform.account_name))
if (isOK(orderform.expiration_date))
return true;
return false;
}
function testDestination(orderform)
{
var isUSA = (orderform.country.value == "USA" ||
orderform.country.value == "US" ||
orderform.country.value == "usa" ||
orderform.country.value == "us");
orderform.flres.checked = ( isUSA &&
(orderform.state.value == "FL" ||
orderform.state.value == "Florida" ||
orderform.state.value == "florida" ||
orderform.state.value == "fl")
);
if (isUSA) {
if (orderform.shipping[2].checked)
orderform.shipping[0].checked = true;
}
else
orderform.shipping[2].checked = true;
accumPrice(orderform);
}
function padString(num)
{
var str = num + "";
var ndx = str.indexOf(".");
if (ndx != -1) {
ndx += 3;
if (ndx == str.length+1)
str += "0";
else
str = str.substring(0, ndx);
}
else
str += ".00";
return str;
}
function accumPrice(orderform)
{
var quantity = parseInt(orderform.quantity.value);
if (quantity < 1)
quantity = 1;
orderform.quantity.value = quantity;
var price = 79.95 * quantity;
orderform.price.value = padString(price);
var shippingcost = 15;
if (orderform.shipping[0].checked == true)
shippingcost = 3;
orderform.shippingcost.value = padString(shippingcost);
var salestax = 0;
if (orderform.flres.checked == true) {
salestax = price + shippingcost;
salestax *= 60;
salestax += 5;
salestax -= salestax % 10;
salestax /= 1000;
}
orderform.salestax.value = padString(salestax);
var total = price + shippingcost + salestax;
orderform.total.value = padString(total);
}
// -------------------------------------------------------------->
</SCRIPT>
/* order_proc.c -- An order-entry processing cgi script. It accepts buyer and order data from a Web page and mails an order to a seller
*/
#include <stdio.h>
#define PRODUCT "MidiFitz"
#define mail_program "/usr/lib/sendmail -t"
#define order_processor "midifitz@midifitz.com"
#define order_taker "midifitz@midifitz.com"
#define link_to "http://www.midifitz.com/index.html"
void PageHeader(const char* title);
void PageTrailer(void);
void PageHead(int level, const char* hed);
void PageLine(const char* line);
void PageParagraph(const char* text);
void PageLink(const char* link, const char* msg);
int main()
{
char line[601];
FILE* fp;
if (fgets(line, 600, stdin) != 0) {
char* cp = line;
while (*cp) {
if (*cp == '&')
*cp = '\n';
else if (*cp == '+')
*cp = ' ';
cp++;
}
}
PageHeader(PRODUCT " On-line Order System");
if ((fp = popen(mail_program, "w")) != NULL) {
fprintf(fp,
"To: " order_processor "\n"
"From: " order_taker "\n"
"Subject: " PRODUCT " on-line order\n"
"%s\n", line);
pclose(fp);
PageParagraph("Your " PRODUCT " order has been placed.");
}
PageLink(link_to, "Click to return");
PageTrailer();
return 0;
}
void PageHeader(const char* title)
{
puts("Content-type: text/html\n");
puts("<HTML>");
puts("<HEAD>");
if (title != 0)
printf("<TITLE>%s</TITLE>\n", title);
puts("</HEAD>");
puts("<BODY>");
}
void PageTrailer(void)
{
puts("</BODY>");
puts("</HTML>");
}
void PageHead(int level, const char* hed)
{
printf("<h%d>%s</h%d>\n", level, hed, level);
}
void PageLine(const char* line)
{
printf("%s<BR>\n", line);
}
void PageParagraph(const char* text)
{
puts("<P>");
PageLine(text);
}
void PageLink(const char* link, const char* msg)
{
printf("<CENTER><A HREF=%s>%s</A></CENTER>\n", link, msg);
}