In last month's issue of Borland C++ Developer's Journal, we showed you how to create a class that maintains the addresses of the Program Segment Prefix (PSP) and the environment block for a program. In that article's example, we showed you how to use that class to display both the environment block your program receives from DOS at startup and the environment block for DOS itself.
In this article, we'll show you how to create a class that
correctly maintains the internal data of the DOS environment block.
To demonstrate this, we'll create an example program that
adds a new environment variable to the DOS environment. Before
we look at the code, let's review the responsibilities
our new class will have.
To view or change a variable from a program's copy of the environment block, you'll typically use the library functions getenv() and putenv(). However, if you want to change an environment variable in the environment block for DOS, you'll need to write code that will manage the block's internal structure.
As we mentioned last month, DOS stores the environment variables as end-to-end strings followed by a null string (a zero-length string with a null terminator). Since DOS stores the variables this way, you'll need to take some precautions before changing them.
If you're going to add a new environment variable, you can merely replace the null string with the string for the new variable. However, if you're going to remove an existing variable or change a variable's value (which involves removing the existing variable and then adding the new version at the end), you may need to rearrange some of the variables that follow the existing variable.
Finally, you need to make sure that when you add a new environment
variable, the size of the new environment block won't be
larger than the space DOS has allocated for the existing environment
block. Otherwise, your program may overwrite the data for another
part of DOS. Now let's see how we can implement these features
in a class we'll call EnvBlock.
An object of the EnvBlock class will be responsible for three things: copying the existing environment variables into an array, providing a standard interface for retrieving and changing individual variables, and copying the new environment variables back out to the existing environment block. To make the class easy to use, we'll divide these responsibilities among the class's member functions.
First, we'll provide a constructor function to initialize an EnvBlock object's internal data structures. When we finish using an EnvBlock object and delete it, our destructor function will deallocate the data structures that the constructor creates.
To allow us to access the environment variables, we'll provide two member functionsgetenv() and putenv()that duplicate the behavior of the C library functions that have the same names. Finally, we'll create two utility member functionsaddenv() and delenv()to make our class easier to change in the future.
To see how we've implemented the EnvBlock class,
see Listing A, where we show a sample program that uses
this class along with the PSP class from last month.
In this program, we use a PSP object and an EnvBlock
object to add a new DOS environment variable. Before we look at
the program, let's examine each of the EnvBlock
class's member functions in detail.
The constructor (EnvBlock::EnvBlock()) sets the value
of each data member in the class. When we call the constructor
for the EnvBlock class, we'll pass it one argumentstart
which contains the address of the environment block we want to
view or modify. The line
startOfEnv(start) {
initializes the startOfEnv data member with the start argument.
Inside the body of the constructor, we create a temporary variable appropriately named tempAddress. The tempAddress variable is a huge character pointer that we'll use to retrieve values from in and around the environment block. (The tempAddress variable needs to be a huge pointer in order for us to change its value correctly.)
To set the value of the envSpaceSize data member, we first set the tempAddress variable to the address of the environment block's size data. We then set the variable envSpaceSize with the value from this location multiplied by 16. For more information on finding this address, see the accompanying article Calculating the environment block's size.
Finally, we call the addenv() private member function
with the tempAddress pointer to create a character array
(commonly called a string) for each environment variable. Afterwards,
the function stores the address of each variable's new
string in the envVars array (an array of strings).
The destructor (EnvBlock::~EnvBlock()) copies the strings from the envVars array to the old environment block as end-to-end strings. However, if we make changes to the variables that would cause the block to grow larger than the space available (envSpaceSize), the destructor will copy only as many as it can fit in the existing space. The destructor will ignore any environment variables we don't have room for.
Finally, we'll deallocate each string we allocated in the
constructor. We'll do this by using the delete
operator on each element in the envVars array.
The EnvBlock::getenv() member function mimics the behavior of the getenv() library function. Unfortunately, the library function works only with your program's copy of the environment block. This member function will return a pointer to a variable you've copied from the DOS environment block (if you call the constructor with the DOS environment block address).
In general, you'll call the getenv() member function
with the name of an environment variable you want to find. However,
if you want to remove an environment variable, you can call this
function with the form
getenv("LIBPATH=");
Then, getenv() will call the private function delenv() to delete that variable from the list.
If the getenv() member function finds the environment
variable you pass as an argument (without the equal sign), it
returns the address of that variable from the envVars
array. If it can't find the variable or if you remove the
variable (by including the equal sign), it returns 0 (a null pointer).
The EnvBlock::putenv() member function mimics the behavior of the putenv() library function. As with the getenv() function, the library version of the putenv() function works only with your program's copy of the environment block. This member function can change a variable you've copied from the DOS environment block.
The putenv() member function begins by calling the getenv()
member function with the environment variable you pass as its
argument. If you call this function with a new value for the environment
variable, it will then call the private function addenv()
to add the new variable.
If the constructor or the putenv() function needs to
add a variable, it calls the private member function EnvBlock::addenv().
This function will dynamically allocate and add a new character
array and then copy the new environment variable into the array.
If the getenv() function needs to remove a variable,
it calls the EnvBlock::delenv() private member function.
This function will delete the appropriate variable, and then move
each of the subsequent variables one position backward in the
array.
To try out the EnvBlock class, first launch the Borland C++ Integrated Development Environment (IDE). You can use the version 3.1 DOS IDE or the version 4.0 Windows IDE. For the remainder of the article, we'll assume that you're using the 3.1 DOS IDE.
When the IDE menu bar appears, open a new source file window by choosing New from the File menu. When the new window appears, enter the code from Listing A.
When you finish entering the code for ENV2.CPP, choose Save from the File menu. In the Save File As dialog box, enter ENV2.CPP as the name of this file and click OK.
Choose Compiler from the Options menu to display the compiler options submenu. From the submenu, choose Code Generation... to display the Code Generation dialog box, as shown in Figure A. Select the Large radio button in the Model section. Click OK to save this change.
Figure A - You set the memory model to Large in the Code Generation dialog box.
Now, build the program by choosing Make from the Compile menu.
When the compiler finishes, the status dialog box will display
Success : press any key
at the bottom. Choose Quit from the File menu to quit the IDE.
At the DOS command line, view your current environment variables
by entering SET. Typically, you'll see settings
similar to
COMSPEC=C:\COMMAND.COM PATH=C:\BORLANDC\BIN;C:\;\DOS;\UTIL; TEMP=C:\DOS PROMPT=$P$G
Now, run ENV2.EXE by entering ENV2 at the command line. When the program finishes and the command prompt returns (it should run for only a second or so), notice that the command prompt may have changed to C>.
You can view the changes ENV2.EXE made by entering SET
at the command line again. Now you should see something similar
to
COMSPEC=C:\COMMAND.COM PATH=C:\BORLANDC\BIN;C:\;\DOS;\UTIL; TEMP=C:\DOS BCDJ=JUNE
The ENV2.EXE program removed the PROMPT environment variable (which
may have changed the appearance of your command prompt). The program
also added the new environment variable BCDJ and set
its value equal to JUNE.
There are a number of environment variables in DOS that you can
change from the DOS command line. By creating the EnvBlock
class, we've provided a standard interface that allows
you to view or change the DOS environment block data.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.