Runtime Environment

"Real Programmers always confuse Christmas and Halloween because OCT 31 == DEC 25" -- Andrew Rutherford

Command Line Arguments

Usually, we see main prototyped as:
void main(void);
or
int main(void);
However, main is sort of overloaded to take parameters as well:
int main(int argc, char *argv[]);
int main(int argc, char **argv);
As we've seen with arrays as parameters, the declarations above are equivalent. This trivial program simply prints out each argument:
void main(int argc, char *argv[])
{
  int i;
  for (i = 0; i < argc; i++)
    printf("arg%i = %s\n", i, argv[i]);
}
If our program was named foo.exe and we were to invoke the program like this:
foo one two three 911
we would see something like this printed out:
foo
one
two
three
911
Another example:
foo one "two three" four 911
foo
one
two three
four
911
Within Visual Studio we see:
E:\Data\Courses\Summer2004\CS220\Code\Chapter16\RTL\Debug\foo.exe
one
two three
four
911
because the IDE invokes the program using the entire path.

A more compact way of printing the arguments using pointers instead of subscripts:

void main(int argc, char **argv)
{
  while (*argv)
    printf("%s\n", *argv++);
}
Diagram of the arguments when invoked as:
foo one "two three" four 911


Note: Because argv is an array of pointers to characters (strings), you can only pass strings to a program. If you want to use the parameter as a number, you will have to convert it from a string to a number yourself. See the Data Conversion section in the C Runtime Library.

Example driver code for the uucode project:


include <stdio.h>  /* printf */
include <stdlib.h> /* atoi   */
include "uucode.h"

int main(int argc, char **argv)
{
  int mode;

    /* If less than 1 argument is provided, remind the user */
    /* how to use the program.                              */
  if (argc < 3)
  {
    printf("\n");
    printf("  Usage: uucode {mode} {inputfile} [decode_path]\n\n");
    printf("  mode = 1 for encoding, 2 for decoding\n");
    printf("  inputfile = file to encode/decode\n");
    printf("  decode_path = filename to store in encoded file (only for encoding)\n\n");
    printf("  Examples: uucode preamble.txt mypreamble.txt > preamble.uue\n");
    printf("            uucode preamble.uue\n\n");
    return -1;
  }

    /* First argument is the mode, 1=encode, 2=decode */
  mode = atoi(argv[1]);

    /* Encode */
  if (mode == 1) 
  {
      /* If only 2 args, the input/remote filename are the same */
    if (argc == 3)
    {
      if (uuencode(argv[2], argv[2]))
        printf("Encoding failed: %s, %s\n", argv[2], argv[2]);
    }
      /* The input filename and remote filename are different   */
    else 
    {
      if (uuencode(argv[2], argv[3]))
        printf("Encoding failed: %s, %s\n", argv[2], argv[3]);
    }
  }
    /* Decode */
  else if (mode == 2)
  {
    if (uudecode(argv[2]))
      printf("Decoding failed: %s\n", argv[2]);
  }
  else  
  {
    printf("Bad mode: %i  Should be 1 or 2.\n", mode);
  }
  return 0;
}

Environment

There's actually another prototype for main which looks like this:
int main(int argc, char **argv, char **env);
where env is similar to argv in that it is a pointer to a pointer to a char. These pointers point to the strings in the environment.
void main(int argc, char **argv, char **env)
{
  while (*env)
    printf("%s\n", *env++);
}
The output from the above program running on my computer looks like this. Notice that this program produces the same results as the SET command typed at the console:
C:\> set     type 'set' and press return
We can experiment with this in C:
const char *GetEnvironmentSetting(const char **env, const char *key)
{
  int len = strlen(key);
  while (*env)
  {
    if (!strncmp(key, *env, len))
      return *env + len + 1;

    env++;
  }
  return NULL;
}

void main(int argc, char **argv, char **env)
{
  const char *value = GetEnvironmentSetting(env, "MyTemp");
  if (value)
    printf("MyTemp is %s.\n", value);
  else
    printf("MyTemp is not set.\n");
}

The environment is a very powerful and easy way to seamlessly configure software. You should try exploiting this concept.

Final notes on main. Not all implementations will support the environment pointer:

ANSI C:

int main(void);                       
int main(int argc, char **argv);      
Traditionally accepted implementations:
int main(void);
int main(int argc, char **argv);
int main(int argc, char **argv, char **env);                                      
extern char *environ[];   // External global variable  
Finally, there is a getenv function (stdlib.h) that you can call anywhere in your program that will return the value a specific environment string:
char *getenv(char const *string);
This is almost identical to the GetEnvironmentSetting function implemented above and can be used without the parameter to main:
void main(void)
{
  value = getenv("MyTemp");
  if (value)
    printf("MyTemp is %s.\n", value);
  else
    printf("MyTemp is not set.\n");
}


Custom exit Code

You can specify which function to call when your program terminates by using the atexit function.
int atexit( void ( *func )( void ) );
This example simply returns from main, which causes the program to terminate:
void MyExit(void)
{
  printf("In MyExit function...\n");
}

void main(void)
{
  atexit(MyExit);
  printf("In main...\n");
}
Output:
In main...
In MyExit function...
This code actually calls exit to terminate the program prematurely (but safely):

void MyExit(void)
{
  printf("In MyExit function...\n");
}

void SomeFn(void)
{
  printf("In SomeFn calling exit...\n");
  exit(0);
}

void main(void)
{
  atexit(MyExit);
  printf("In main...\n");
  SomeFn();
}
Output:
In main...
In SomeFn calling exit...
In MyExit function...
This program sets up several exit functions:

void MyExit1(void)
{
  printf("In MyExit1 function...\n");
}

void MyExit2(void)
{
  printf("In MyExit2 function...\n");
}

void MyExit3(void)
{
  printf("In MyExit3 function...\n");
}

void SomeFn(void)
{
  printf("In SomeFn calling exit...\n");
  exit(0);
}

void main(void)
{
  atexit(MyExit1);
  atexit(MyExit2);
  atexit(MyExit3);
  printf("In main...\n");
}

And we can see that they are called in the reverse order in which they were assigned:

Output:

In main...
In MyExit3 function...
In MyExit2 function...
In MyExit1 function...

Executing Programs from C Code

Suppose you have an executable that you want to use within your own program. How can you call it? The easiest way is with the system function:
int system( const char *command );
Using it in a program like this:
void main(void)
{
  system("notepad");
}
will cause the Windows Notepad application to run. You can also pass command line arguments to other programs as well. The example below calls the Microsoft C/C++ compiler and passes a filename to it to compile:
void main(void)
{
  system("cl E:\\Projects\\Game1\\main.c");
}
A simple program to execute almost any command from C:
void main(void)
{
  char buffer[100];
  printf("What command do you want to execute?\n");
  fgets(buffer, 100, stdin);
  system(buffer);
  printf("This line will be printed after the system call above terminates.\n");
}
Example run (program is rtl.exe):
E:\Data\Courses\Summer2002\CS220\Code\Chapter16\RTL\Debug>rtl
What command do you want to execute?
dir d:\ e:\
Volume in drive D is Applications
 Volume Serial Number is 1084-18F9

 Directory of d:\

04/10/2002  07:03a      <DIR>          borlandc
05/24/2002  11:22a      <DIR>          CygWin
12/06/2001  06:08p      <DIR>          Data
12/10/2001  05:56p             126,875 dirinfo.mcd
12/07/2001  03:37p      <DIR>          JBuilder4
07/19/2002  12:41p      <DIR>          Program Files
07/05/2002  10:25a      <DIR>          stuff
               1 File(s)        126,875 bytes
               6 Dir(s)     604,979,200 bytes free

 Volume in drive E is Data
 Volume Serial Number is DCA8-DC74

 Directory of e:\

05/10/2002  01:59p      <DIR>          CVSRoot
07/19/2002  07:54a      <DIR>          Data
01/15/2002  09:49a              16,663 dirinfo.mcd
12/07/2001  12:36p      <DIR>          Documents and Settings
07/22/2002  10:06a      <DIR>          Download
02/05/2002  10:46a              29,607 Export_HKEY_CURRENT_USER_Software_Microsoft_DevStudio.reg
12/06/2001  02:51p      <DIR>          i386
07/15/2002  08:59a      <DIR>          images
02/01/2002  09:06a      <DIR>          Installs
12/07/2001  12:29p      <DIR>          Program Files
05/23/2002  12:44p                   0 s1cc
05/23/2002  12:44p                   0 s1cc.1
05/23/2002  12:44p                   0 s1cc.2
05/23/2002  12:44p                   0 s1cc.3
05/23/2002  12:44p                   0 s1cc.4
02/01/2002  11:58a      <DIR>          Share
02/05/2002  10:39a      <DIR>          sp5
07/05/2002  04:10p      <DIR>          stuff
07/17/2002  06:59a      <DIR>          temp
03/01/2002  05:02p              25,761 TempMon1.gif
04/23/2002  08:43a      <DIR>          WebReaper
12/07/2001  12:39p      <DIR>          WINNTX
               8 File(s)         72,031 bytes
              14 Dir(s)   4,069,343,232 bytes free


This line will be printed after the system call above terminates.

Using exec and Others

The exec family of functions is similar to the system function, but has more capabilities.
Passing a (variable-length) list to exec:
void main(void)
{
  int retval;

    // Using a list only (must specify path)
  retval = _execl("c:\\winnt\\system32\\notepad", "c:\\winnt\\system32\\notepad", "c:\\somefile.txt", NULL);

    // We only reach this if the call to _execl fails
  printf("Exec failed. retval = %i, errno = %i\n", retval, errno);
  perror("Message");
}
If we want to have the system find the command in the environment (the PATH variable), we can use this:
  // Using a list and getting path from the environment
retval = _execlp("notepad", "notepad", "c:\\somefile.txt", NULL);
Passing a vector to exec:
void main(void)
{
  const char *args[] = {"c:\\winnt\\system32\\notepad", "c:\\somefile.txt", NULL};
  int retval;

    // Using a vector only (must specify path)
  retval = _execv("c:\\winnt\\system32\\notepad", args);

    // We only reach this if the call to _execv fails
  printf("Exec failed. retval = %i, errno = %i\n", retval, errno);
  perror("Message");
}
If we want to have the system find the command in the environment (the PATH variable), we can use this:
  // Using a vector and getting path from the environment
retval = _execvp("notepad", args);
It may seem redundant to specify the name of the program twice. The first occurrence is the program that the OS is going to run. The second occurrence will be placed into argv[0] for the program to be run. Common sense might indicate that they should be the same, but it isn't enforced.
  const char *args[] = {"notepad", "c:\\somefile.txt", NULL};
  _execvp("notepad", args);
or
_execlp("notepad", "notepad", "c:\\somefile.txt", NULL);
The spawn family of functions is similar to the exec family of functions, but has slightly different capabilities. The behavior of these functions depends on the value of the mode parameter and can be one of these:
Notes

Additional source code shown in class

exec code (Also command line, environment, atexit, system, spawn)

popen code