Borland Online And The Cobb Group Present:


May, 1994 - Vol. 1 No. 5

Accessing the parent process environment variables with C++

As you may know, Borland C++ provides you with a number of tools you can use to see and manipulate the DOS environment variables. Unfortunately, the environment variables that DOS programs use are only copies of the variables the programs receive from their parent process.

If you start a program from a DOS command line, the parent process for the program is COMMAND.COM. Any changes you make to the environment variables from inside a program are lost as soon as the program returns control to COMMAND.COM.

However, you can access the environment variables for the parent process by using the information from a program's Program Segment Prefix (PSP). In this article, we'll show you how to use a C++ class to access the environment variables for a program's parent process.

Protecting the environment

When you run a DOS program, that program receives a copy of the current environment block­­a region of memory in which DOS stores the current program's environment variables, as shown in Figure A. Because each program has its own copy of the environment block, a program can change its block without affecting the block that COMMAND.COM (or some other parent process) uses.


Figure A - DOS passes a copy of the current environment block to programs as they start.

Inside a Borland C++ program, the startup code for the program copies the individual environment variables to separate strings in an array. From inside your program, you can view and manipulate these environment variable strings by using one of three functions: the environ global variable, the env command-line argument, and the getenv() function.

These functions access the environment variable strings that the startup code copied from the program's environment block. Only one of the three functions­­getenv()­­will ever use the environment block that came from DOS. Figure B shows how the functions handle the environment information.


Figure B - Normally, you don't have access to the actual environment block for your program.

We'll look individually at these methods for accessing environment variable strings. First, we'll examine the obvious method: using the environ global variable.

The environ global variable

When Borland C++ creates the array of strings for the environment variables, it assigns the value of environ to point to this array. You can view any of the environment variables in the array by using the syntax

char* aVar = environ[index];

In this statement, the value of index will select an environment variable and set aVar to point to that variable's string.

Generally, using the environ global variable is the easiest and safest way to view an environment variable. For example, if you change any of the environment variables, Borland C++ will update the environ pointer (if it moves) or one of the pointers in the environ array (if one of the strings moves).

The env command-line argument

At program startup, Borland C++ sets the env command-line argument equal to the initial value of environ. However, Borland C++ doesn't update the value of env if the environ array moves. Therefore, you should be cautious about using env­­except near the beginning of the main() function.

The getenv() function

If you know the name of an environment variable, you can retrieve its value by using the getenv() function. Unfortunately, if you don't know the names of all the current environment variables, you won't be able to view them with the getenv() function.

The getenv() function will return a pointer to an environment variable inside the environment block if you haven't changed that variable. If you change the variable (using the putenv() function), the getenv() function will return a pointer that points inside that variable's string in the environ array.

Getting the real environment block

To examine the environment block that a program receives from DOS, you'll need to obtain a pointer to this block of memory. Fortunately, each program's PSP contains the address of the program's environment block as well as a pointer to the parent process's PSP. For more information on using these pointers, see Looking at the parent process's PSP.

Once you have a pointer to the beginning of the environment block, you can examine any of the variables in the block. Each environment variable is a null-terminated string. Inside the environment block, the strings appear one after the other. For example, the environment variables

SHELL=C:\DOS\COMMAND.COM;
PROMPT=$g;
PATH=C:\;

appear in the environment block as

SHELL=C:\DOS\COMMAND.COM;*PROMPT=$g;*PATH=C:\;**

For illustration, we've substituted an asterisk (*) for the null byte that ends each string. Notice that a zero-length string (a single null byte) appears after PATH=C:\;* to signify the end of the environment block.

To look at the strings Borland C++ stores in the environ array, you can simply move through the array, element by element. To look at each variable in the environment block separately, you'll need to put a temporary pointer at the beginning of the environment block and then advance it to the end of each string to get the next string.

Finally, you can wrap a C++ class around some of the PSP access code and data to simplify using the PSPs and the environment blocks. To see how we can view the environment variables for a program and those of its parent process, let's put all of this together in an example program.

Examining the environment variables

Launch the Borland C++ Integrated Development Environment (IDE). (For the remainder of the article, we'll assume that you're using version 3.1 for DOS. If you're using version 4.0, you'll need to create a new project for a DOS Standard target that uses the Large memory model.) When the IDE's desktop appears, choose New from the File menu and enter the code for ENV.CPP from Listing A.


Listing A: ENV.CPP

#include <iostream.h>// stream functions
#include <dos.h>     // _psp & environ
#include <string.h>  // strlen()
#include <stdlib.h>  // putenv()

// Program Segment Prefix class
class PSP{
  public:
   PSP(unsigned int pspSegment){
      pspFarPointer =
        (char *)(char _seg *)pspSegment;
   }

   unsigned int getParentPSP(){
      return *((unsigned int*)
               (pspFarPointer + 22));
   }

   char* getEnvironmentBlock(){
      return *((char _seg **)
               (pspFarPointer + 44));
   }

  private:
   char* pspFarPointer;
};

// Program Body
int main(){
   putenv("BCDJ=MAY");

    // Global PSP variables
   PSP myPSP(_psp);
   PSP myParentPSP(myPSP.getParentPSP());

   cout << "\n- COMMAND.COM ENVIRONMENT -\n";
   char huge * envVar =
       myParentPSP.getEnvironmentBlock();
   while(*envVar != 0){
      cout << (char*)envVar << endl;
      envVar = envVar + 1 + strlen((char*)envVar);
   }

   cout << "\n--- ENV.EXE ENVIRONMENT ---\n";
   int count = 0;
   char huge * temp2 = environ[count];
   while(*(environ[count]) != 0){
      cout << (char*)temp2 << endl;
      ++count;
      temp2 = environ[count];
   }
   return 0;
}

Before you compile this application, select Compiler from the Options menu, and then choose Code Generation... from the Compiler submenu. In the Code Generation Options dialog box, you select the Large radio button in the Model group and then click OK.

Now you can compile the application by choosing Make from the Compile menu. When the IDE finishes linking the application, the status dialog box displays

Success : Press any key

along the bottom of the box.

If you look at the code from ENV.CPP, you'll notice that the PSP class does a few useful things. First, its constructor initializes a pointer called pspFarPointer to the PSP itself from an unsigned integer that has the PSP's segment address. Any PSP member functions can use pspFarPointer without recalculating the address.

Second, the PSP class provides the getEnvironmentBlock() member function to calculate a pointer to PSP's environment block. The return value is a char pointer, since the environment block consists of a series of strings.

Finally, the PSP class provides the getParentPSP() function to retrieve the segment address of the parent process's PSP. Since this function returns an unsigned integer, you can use this value to create an instance of the PSP class for the parent process.

To help you see that using the putenv() function to change your program's copy of the environment variables doesn't change the parent process's environment variables, we use putenv() to add a new environment variable called BCDJ. Then, we create two instances of the PSP class.

To create myPSP, we use the global variable _psp, an unsigned integer that holds the value of the PSP's segment address. To create myParentPSP, we use the PSP address value that we get from the program's PSP via the myPSP.getParentPSP() member function call.

Now, choose Save from the File menu. In the Save File As dialog box, enter ENV.CPP in the Save File As entry field and click OK. Quit the IDE by choosing Quit from the File menu.

At a DOS command prompt, enter ENV. When it runs, ENV.EXE will display information similar to that shown in Figure C.


Figure C - ENV.EXE will display its own and its parent process's environment variables.

- COMMAND.COM ENVIRONMENT -
SHELL=C:\DOS\COMMAND.COM /P
PROMPT=$i$p$g
PATH=C:\DOS;C:\WINDOWS;C:\WINDOWS\SYSTEM;

--- ENV.EXE ENVIRONMENT ---
SHELL=C:\DOS\COMMAND.COM /P
PROMPT=$i$p$g
PATH=C:\DOS;C:\WINDOWS;C:\WINDOWS\SYSTEM;
BCDJ=MAY

ENV.EXE displays the environment variables for COMMAND.COM in the first section. In the second section, you can see the addition of the BCDJ environment variable to the list of environment variables.

Not from the IDE

If you try to run ENV.EXE from the IDE, your program won't be able to access the IDE's environment block. This is because Borland intentionally alters the environment block pointer in its own PSP to prevent you from disturbing the IDE's environment variables.

If you want to run ENV.EXE while the IDE is running, choose DOS Shell from the File menu. Since the IDE runs a DOS shell as a separate child process, the shell will have its own standard environment block pointer in its PSP.

Conclusion

As you can tell from ENV.EXE's output, calling the putenv() function changes a program's copy of the environment variables­­not the parent process's copy of the environment block. However, you can use the information in a program's PSP to examine either environment block. In a future issue, we'll enhance the PSP class to let you make changes to the current program's environment block or to the parent process's environment block.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


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.