May, 2000 - NT Internet Goodies

 

Column

by Andy Bruce

Last time we looked at more Web security and how to speed up the computing process. This time, we look at Perl for NT and the various goodies freely available. Don't worry if you're not a Perl guru--I'll give you enough information here to get you started with this amazing language. Also in this issue, I deliver on the promised "scroll bars for 98 DOS boxes" program.

Why Do We Care About Perl?

Traditionally, Perl has been the back-office language of the Internet. And this is a column called NT Internet Goodies. And Perl comes in really cool flavors for NT--with lots of NT-specific extras that you can use in your programs. Put it all together, and it sounds like a recipe for a great column to me!

Casting Perls Before ...?

(Ultra-Brief) Introduction to Perl

Yes, I know, this isn't a programming column. However, I'll devote a couple of paragraphs to expounding my interpretation of the Perl philosophy (you'll find that Perl is a great supporter both of individuality and the right to free expression). Perl is a general-purpose text manipulation language. The acronym has no official meaning, but the "usual" understanding is that it stands for Practical Extraction and Reports Language (or Pathologically Eclectic Rubbish Lister--take your pick). That being said, keep in mind that CGI developers around the world use Perl for many back-office Web tasks, including database management, financial packages, and much more. It's certainly not a simple-minded replacement for UNIX tools like sed or awk.

Perl was developed by C programmers as a tool to get things done. The emphasis has been and always will be on practicality and immediate utility. Also, this means that many of the language features were driven by a good C programmer's need to minimize redundancy. As an example, consider these three implementations of "Hello, world!":

Implementation 1:
          print ("Hello, world!\n");
Implementation 2:
          print "Hello, world!\n" ;
Implementation 3:
          $_ = "Hello, world!\n" ;
          print ;

Yes, all three end up doing exactly the same thing. Yes, all three are completely legal. Yes, all three are "correct" ways to solve the problem. In short, they demonstrate The Perl Way--give me flexible tools to do what I want, and then get the heck out of the way!

Where to Get Perl for NT

The best place I've found for all sorts of information on Perl for Windows is at http://www.netaxs.com/~joc/perlwin32.html. On this site, you can get up-to-date links on locations both for Perl source code as well as binaries (available from ActiveState as ActivePerl at )http://www.ActiveState.com/Products/ActivePerl/Download.html). Being the adventurous type, I naturally opted for the source code and chose to build it myself.

Note: The instructions below work equally well for both the regular Perl 5.00503 and the source code from the ActiveState Web site. However, you'll need to download the ActiveState Perl binary to use the Perl Package Manager ("PPM") referenced in the next section.

I downloaded the Perl source and extracted it to F:\Perl. The top-level directory was p500503, and the only configuration I had to do was to:

  • edit the F:\Perl\win32\Makefile to change the top path I>F:\Perl
  • rename the p500503 directory to 5.00503 to match the default INST_DIR variable in the Makefile.

Then came building and installing the Perl interpreter itself:

  • Build the binaries by using nmake build from the F:\Perl\5.00503\win32 directory. Twiddle your fingers...done! (I just upgraded to a faster box with more memory.) The build ran in less than 10 minutes.
  • Test the binaries using nmake test from the same directory. It only took a minute to run the 5000+ tests the Perl writers deem required!
  • Install the program using nmake install from the same directory. Lots and lots of files get copied (in my case, to F:\Perl\5.00503\bin and F:\Perl\5.00503\lib).
  • Update your system path to use both installation binary directories (in my case, the F:\Perl\5.00503\bin and the F:\Perl\5.00503\bin\MsWin32-x86 directories).
  • If you want, update the NT registry to associate common Perl files (files ending with .pl) with the Perl binary. Doing so allows you to execute a Perl script simply by double-clicking the script from Explorer or by typing the name of the Perl script at the NT command prompt. You can create these associations by using:
assoc .pl=Perl
ftype Perl=<full path to perl> %1 %*

In my case, the last line is ftype Perl=F:\Perl\5.00503\bin\MsWin32-x86\Perl.exe %1 %*.

Cool Perl Programming Stuff

A quick trip through the list of NT-specific Perl enhancements lets you get an idea of just what kind of power programming you can perform with Perl. For example, let's say that you want to get to CPU performance and configuration information. You could write a C program that gathers all that information (from the NT Performance Registry and other places) or you could simply download the Win32::IPerfMon package and use it to get the information for you.

In fact, many very cool Perl programming APIs for NT are written by a single author, at http://www.generation.net/~aminer/Perl/. However, to use the extensions listed on this page, you'll need the Perl Package Manager (PPM), which comes bundled with the ActivePerl installation package (not the regular Perl 5.00503 source code).

Cool Perl Scripts for NT

Not a programmer? Then you'll be interested to hear that you can use Perl scripts to do all sorts of useful things. For example, you can emulate the UNIX cd command by using the cdp script. Or you can use the search script to scan an entire directory tree for a regular expression search string (similar in concept to grep <expr> `find . ­name xxx ­print` for UNIX-heads). You can even get the handy whence command to tell you which directory a particular binary gets executed from, as in whence java.

One caveat with all of these utilities: they all depend on some quite tricky interactions with the NT shell command. In fact, when you download the scripts, you'll find that they're all batch files. However, closer inspection reveals them to be batch files on steroids! They're self-modifying batch files that incorporate complete Perl scripts. The batch files invoke the Perl compiler on the embedded Perl script, which then modifies the batch file (in memory, not on disk). A whacky concept, but it works!

Since I've been doing lots of Perl scripting lately, I'll be collecting and posting interesting tidbits that can make all of our lives easier.

A Half-Baked Goodie: Scroll bars for DOS Boxes under 95/98

Let's live dangerously and see what life is like away from Windows NT! I see by reading the column before the last that I promised I'd show how you can add scroll bars to DOS boxes for Windows 95/98. This is one thing that's always bugged me, since 95/98 only allows a maximum of 50 lines per DOS box (pretty lame).

My first thought was that I wanted to modify the actual DOS command box itself to add scroll bars to it. Windows offers a nice set of APIs for modifying an application's console window (via AllocConsole, SetConsoleScreenBufferSize, and so on). However, there's a catch here: only the owning application can perform these tasks! So, how could I hook into the DOS command box code to do this? The only way I could find was to use the CreateRemoteThread API to attach to the DOS box. However, for that to work I needed an entry point inside the DOS box that could start the thread. And to create that, I'd need to write the code bytes necessary directly to the DOS box's address space (basically, create my own hook!). That was more work than I wanted to do.

So my actual approach was to spawn a DOS box as a child process, and make the DOS box use the owning process's console. This was lots more work than it may sound like, since my application had to account for lots of different gotchas. And in the process, I picked up some information you might find useful in your own development.

I (re)discovered just what a pain it is to spawn a DOS command shell from your C program--if you want the command shell to use a different input/output handle! Turns out that your calling program can (and does) hang if you redirect input or output when spawning a child program. The way you overcome this is to:

  • create your customized input/output handles
  • spawn an intermediate child program that inherits these handles
  • use the intermediate child program to spawn the program you initially wanted to spawn, as in the following sample intermediate program:
/* intermediate program used to spawn command shell */
int
main( int argc, char **argv ) {
// create "actual" child process
	STARTUPINFO si = {0};
	si.cb = sizeof(si);
	si.dwFlags    = STARTF_USESTDHANDLES;
	si.hStdInput  = GetStdHandle (STD_INPUT_HANDLE);
	si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
	si.hStdError  = GetStdHandle (STD_ERROR_HANDLE);
	PROCESS_INFORMATION pi = {0};
	BOOL bRet = CreateProcess( NULL, argv[1],
	     NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi ) ;
	if( bRet ) {
		     // this is what we do that our parent can't
WaitForSingleObject( pi.hProcess, INFINITE ) ;
		     CloseHandle( pi.hProcess ) ;
		     CloseHandle( pi.hThread ) ;
	} //end if
	return 0 ;
} //end main

The only thing that the intermediate application does is spawn the requested child process and wait for it to terminate. I'm still not exactly sure why this is the case, but after reading the appropriate on-line Microsoft knowledgebase bug (and trying to run without this extra step), I'm willing to believe it!

Another interesting tidbit of information is how to process the redirected input and output. Keep in mind that the parent application (CONSPAWN) actually reads input from the keyboard and displays output to the console. Basically, I maintain a couple of threads. Each time a keystroke occurs, I post the keystroke to the pipe that the spawned command shell uses for input (which CONSPAWN uses for output). The command shell reads the input pipe and takes action upon the keystroke, which results in data going to its output pipe. When the CONSPAWN application detects data available on the command shell's output pipe (which CONSPAWN reads as input data), CONSPAWN simply prints the output information directly to the actual console that the user sees. The result? The user types input and sees output in the scrollable window created by CONSPAWN, even though the spawned command shell knows nothing about such a window! Here's the piece of the CONSPAWN application that reads input from the spawned command shell's output pipe:

/* read input from the spawned command shell's output pipe */
void __cdecl
outputThread( void *p ) {
	// we passed this as an arg to __beginthread
	HANDLE hPipeOutputRead = (HANDLE)p ;
	TCHAR buffer[1024] ;
	// main loop
	while( !gbQuit ) {
		// reset the buffer
		memset( strBuffer, 0, sizeof( strBuffer ) ) ;
		// read the data. upon error (FALSE from ReadFile),
		// terminate the loop (must be because of an error
		// reading data from the spawned command shell).
		DWORD dw ;
		BOOL b = ReadFile( hPipeOutputRead,
			strBuffer, sizeof( strBuffer ), &dw, NULL ) ;
		if( !b ) {
			gbQuit = TRUE ;
			break ;
		} //end if
		// no error, so write the data from the command shell
		// to our console
		writeFormattedBuffer( GetStdHandle( STD_OUTPUT_HANDLE ),
			strBuffer, dw ) ;
	} //end while
	// indicate command shell's output thread is finished
	gbOutputTerminated = TRUE ;
} //end outputThread

The Good Points

You can specify options to this application, including the number of rows and column you want. The application is especially useful for viewing usage screens for programs that don't allow you to redirect output to a text file. For example, you can scroll backwards indefinitely for the big project "make" that you started on your 98 box.

The Bad Points

It's problematic to make an interactive command shell using my approach. For example, assume you type the characters "foo" and then the backspace key. CONSPAWN properly displays the "foo" on the screen, but then it gets hosed when you hit backspace! Basically, this is because CONSPAWN uses pipes to redirect input and output to the command shell. Pipes are, by definition, LIFO (last-in-first-out) constructs. However, pressing backspace results in some internal processing within the command shell that makes the command shell reposition its internal cursor. Pipes can't handle a concept like repositioning--it's impossible to do a "seek" on a pipe! Thus, you can't use any editing keys within a CONSPAWN session.

This may seem like a really bad setback, and it is. However, CONSPAWN is still a nice tool to get working scroll bars for a Windows/98 command prompt, so I'm not accepting complaints on it! (The solution to this problem is to grab a copy of the internal screen buffer the command shell manipulates and update the entire screen every time a key is pressed. I'll get to that eventually... [sigh]) As it is, I spent an inordinately long time developing this silly utility, and I'm going to leave it alone for a bit!

Finally, if you notice output running off of the screen, just click the scroll bar to scroll the screen contents down to where you can see the output you want.

Where to Get It

You can download the CONSPAWN application (including source and project files) from the normal Web page for this issue of Enterprise Solutions.


Andy Bruce has been writing software for various operating systems and assorted languages for more than 12 years. He is a primary author of several shrink-wrap products, including Landmark Systems' PerformanceWorks suite and Savant Corporation's Q for Oracle. He also has written many courses in computer programming for McGraw-Hill NRI. He lives and works in the Washington, D.C. area.