The Machine Perception Toolbox

[Introduction]- [News]- [Download]- [Screenshots]- [Manual (pdf)]- [Forums]- [API Reference]- [Repository ]

 

Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

execnt.c

Go to the documentation of this file.
00001 /*
00002  * Copyright 1993, 1995 Christopher Seiwald.
00003  *
00004  * This file is part of Jam - see jam.c for Copyright information.
00005  */
00006 
00007 /*  This file is ALSO:
00008  *  (C) Copyright David Abrahams 2001. Permission to copy, use,
00009  *  modify, sell and distribute this software is granted provided this
00010  *  copyright notice appears in all copies. This software is provided
00011  *  "as is" without express or implied warranty, and with no claim as
00012  *  to its suitability for any purpose.
00013  */
00014 
00015 # include "jam.h"
00016 # include "lists.h"
00017 # include "execcmd.h"
00018 # include <errno.h>
00019 # include <assert.h>
00020 # include <ctype.h>
00021 
00022 # ifdef USE_EXECNT
00023 
00024 # define WIN32_LEAN_AND_MEAN
00025 # include <windows.h>           /* do the ugly deed */
00026 # include <process.h>
00027 
00028 # if !defined( __BORLANDC__ ) && !defined( OS_OS2 )
00029 # define wait my_wait
00030 static int my_wait( int *status );
00031 # endif
00032 
00033 /*
00034  * execnt.c - execute a shell command on Windows NT and Windows 95/98
00035  *
00036  * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
00037  * The default is:
00038  *
00039  *      /bin/sh -c %            [ on UNIX/AmigaOS ]
00040  *      cmd.exe /c %            [ on Windows NT ]
00041  *
00042  * Each word must be an individual element in a jam variable value.
00043  *
00044  * In $(JAMSHELL), % expands to the command string and ! expands to 
00045  * the slot number (starting at 1) for multiprocess (-j) invocations.
00046  * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
00047  * argument.
00048  *
00049  * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
00050  *
00051  * External routines:
00052  *      execcmd() - launch an async command execution
00053  *      execwait() - wait and drive at most one execution completion
00054  *
00055  * Internal routines:
00056  *      onintr() - bump intr to note command interruption
00057  *
00058  * 04/08/94 (seiwald) - Coherent/386 support added.
00059  * 05/04/94 (seiwald) - async multiprocess interface
00060  * 01/22/95 (seiwald) - $(JAMSHELL) support
00061  * 06/02/97 (gsar)    - full async multiprocess support for Win32
00062  */
00063 
00064 static int intr = 0;
00065 static int cmdsrunning = 0;
00066 static void (*istat)( int );
00067 
00068 static int  is_nt_351        = 0;
00069 static int  is_win95         = 1;
00070 static int  is_win95_defined = 0;
00071 
00072 
00073 static struct
00074 {
00075         int     pid; /* on win32, a real process handle */
00076         void    (*func)( void *closure, int status );
00077         void    *closure;
00078         char    *tempfile;
00079 
00080 } cmdtab[ MAXJOBS ] = {{0}};
00081 
00082 
00083 static void
00084 set_is_win95( void )
00085 {
00086   OSVERSIONINFO  os_info;
00087 
00088   os_info.dwOSVersionInfoSize = sizeof(os_info);
00089   os_info.dwPlatformId        = VER_PLATFORM_WIN32_WINDOWS;
00090   GetVersionEx( &os_info );
00091   
00092   is_win95         = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
00093   is_win95_defined = 1;
00094   
00095   /* now, test wether we're running Windows 3.51                */
00096   /* this is later used to limit the system call command length */
00097   if (os_info.dwPlatformId ==  VER_PLATFORM_WIN32_NT)
00098     is_nt_351 = os_info.dwMajorVersion == 3;
00099 }
00100 
00101 int maxline()
00102 {
00103     if (!is_win95_defined)
00104         set_is_win95();
00105     
00106     /* Set the maximum command line length according to the OS */
00107     return is_nt_351 ? 996
00108         : is_win95 ? 1023
00109         : 2047;
00110 }
00111 
00112 static char**
00113 string_to_args( const char*  string, int*  pcount )
00114 {
00115   int    total    = strlen( string );
00116   int    in_quote = 0,
00117       num_args = 0; /* was uninitialized -- dwa */
00118   char*  line;
00119   char*  p;
00120   char** arg;
00121   char** args;
00122 
00123   *pcount = 0;  
00124 
00125   /* do not copy trailing newlines, if any */  
00126   while ( total > 0 )
00127   {
00128       if ( !isspace( string[total - 1] ) )
00129           break;
00130       --total;
00131   }
00132   
00133   /* first of all, copy the input string */
00134   line    = (char*)malloc( total+2 );
00135   if (!line)
00136     return 0;
00137     
00138   memcpy( line+1, string, total );
00139   line[0]       = 0;
00140   line[total+1] = 0;
00141   
00142   in_quote = 0;
00143   for ( p = line+1; p[0]; p++ )
00144   {
00145     switch (p[0])
00146     {
00147       case '"':
00148         in_quote = !in_quote;
00149         break;
00150         
00151       case ' ':
00152       case '\t':
00153         if (!in_quote)
00154           p[0]    = 0;
00155         
00156       default:
00157         ;
00158     }
00159   }
00160   
00161   /* now count the arguments.. */
00162   for ( p = line; p < line+total+1; p++ )
00163     if ( !p[0] && p[1] )
00164       num_args++;
00165       
00166   /* allocate the args array */
00167   /* dwa -- did you really mean to allocate only 2 additional bytes? */
00168 #if 0 /* was like this */
00169   args = (char**)malloc( num_args*sizeof(char*)+2 );
00170 #endif
00171   args = (char**)malloc( (num_args + 2) * sizeof(char*) );
00172   if (!args)
00173   {
00174     free( line );
00175     return 0;
00176   }
00177   
00178   arg = args+1;
00179   for ( p = line; p < line+total+1; p++ )
00180     if ( !p[0] && p[1] )
00181     {
00182       arg[0] = p+1;
00183       arg++;
00184     }
00185   arg[0]  = 0;
00186   *pcount = num_args;
00187   args[0] = line;
00188   return args+1;
00189 }
00190 
00191 static void
00192 free_args( char** args )
00193 {
00194   free( args[-1] );
00195   free( args-1 );
00196 }
00197 
00198 
00199 /* process a "del" or "erase" command under Windows 95/98 */
00200 static int
00201 process_del( char*  command )
00202 {
00203   char** arg;
00204   char*  p = command, *q;
00205   int    wildcard = 0, result = 0;
00206 
00207   /* first of all, skip the command itself */
00208   if ( p[0] == 'd' )
00209     p += 3; /* assumes "del..;" */
00210   else if ( p[0] == 'e' )
00211     p += 5; /* assumes "erase.." */
00212   else
00213     return 1; /* invalid command */
00214 
00215   /* process all targets independently */
00216   for (;;)
00217   {
00218     /* skip leading spaces */
00219     while ( *p && isspace(*p) )
00220       p++;
00221       
00222     /* exit if we encounter an end of string */
00223     if (!*p)
00224       return 0;
00225       
00226     /* ignore toggles/flags */
00227     while (*p == '/')
00228     {
00229       p++;
00230       while ( *p && isalnum(*p) )
00231           p++;
00232       while (*p && isspace(*p) )
00233           ++p;
00234     }
00235 
00236     
00237     {
00238       int  in_quote = 0;
00239       int  wildcard = 0;
00240       int  go_on    = 1;
00241       
00242       q = p;
00243       while (go_on)
00244       {
00245         switch (*p)
00246         {
00247           case '"':
00248             in_quote = !in_quote;
00249             break;
00250           
00251           case '?':
00252           case '*':
00253             if (!in_quote)
00254               wildcard = 1;
00255             break;
00256             
00257           case '\0':
00258             if (in_quote)
00259               return 1;
00260             /* fall-through */
00261               
00262           case ' ':
00263           case '\t':
00264             if (!in_quote)
00265             {
00266               int    len = p - q;
00267               int    result;
00268               char*  line;
00269               
00270               /* q..p-1 contains the delete argument */
00271               if ( len <= 0 )
00272                 return 1;
00273   
00274               line = (char*)malloc( len+4+1 );
00275               if (!line)
00276                 return 1;
00277                 
00278               strncpy( line, "del ", 4 );
00279               strncpy( line+4, q, len );
00280               line[len+4] = '\0';
00281               
00282               if ( wildcard )
00283                 result = system( line );
00284               else
00285                 result = !DeleteFile( line+4 );
00286   
00287               free( line );
00288               if (result)
00289                 return 1;
00290                 
00291               go_on = 0;
00292             }
00293             
00294           default:
00295             ;
00296         }
00297         p++;
00298       } /* while (go_on) */
00299     }
00300   }
00301 }
00302 
00303 
00304 /*
00305  * onintr() - bump intr to note command interruption
00306  */
00307 
00308 void
00309 onintr( int disp )
00310 {
00311         intr++;
00312         printf( "...interrupted\n" );
00313 }
00314 
00315 #if 0 // the shell is too different from direct invocation; let's
00316       // always use the shell unless forced.
00317 /*
00318  * use_bat_file() - return true iff the command demands the use of a
00319  * .bat file to run it
00320  */
00321 int use_bat_file(char* command)
00322 {
00323     char *p = command;
00324     
00325     char inquote = 0;
00326 
00327     p += strspn( p, " \t" );
00328 
00329     /* spawnvp can't handle any paths with spaces or quoted filenames with no directory prefix */
00330     if ( *p == '"' )
00331     {
00332         char* q = p + 1 + strcspn( p + 1, "\" /\\" );
00333         if ( *q == '"' || *q == ' ' )
00334             return 1;
00335     }
00336         
00337     /* Look for newlines and unquoted i/o redirection */
00338     do
00339     {
00340         p += strcspn( p, "'\n\"<>|" );
00341 
00342         switch (*p)
00343         {
00344         case '\n':
00345             /* skip over any following spaces */
00346             while( isspace( *p ) )
00347                 ++p;
00348             /* return true iff there is anything significant following
00349              * the newline
00350              */
00351             if (*p)
00352                 return 1;
00353             break;
00354             
00355         case '"':
00356         case '\'':
00357             if (p > command && p[-1] != '\\')
00358             {
00359                 if (inquote == *p)
00360                     inquote = 0;
00361                 else if (inquote == 0)
00362                     inquote = *p;
00363             }
00364                 
00365             ++p;
00366             break;
00367             
00368         case '<':
00369         case '>':
00370         case '|':
00371             if (!inquote)
00372                 return 1;
00373             ++p;
00374             break;
00375         }
00376     }
00377     while (*p);
00378     
00379     return p - command >= MAXLINE;
00380 }
00381 #endif
00382 
00383 void execnt_unit_test()
00384 {
00385 #if 0 && !defined(NDEBUG)
00386     /* vc6 preprocessor is broken, so assert with these strings gets
00387      * confused. Use a table instead.
00388      */
00389     typedef struct test { char* command; int result; } test;
00390     test tests[] = {
00391         { "x", 0 },
00392         { "x\n ", 0 },
00393         { "x\ny", 1 },
00394         { "x\n\n y", 1 },
00395         { "\"x\"", 1 },
00396         { "\"x y\"", 1 },
00397         { "\"x/y\"", 0 },
00398         { "\"x\\y\"", 0 },
00399         { "echo x > foo.bar", 1 },
00400         { "echo x < foo.bar", 1 },
00401         { "echo x \">\" foo.bar", 0 },
00402         { "echo x \"<\" foo.bar", 0 },
00403         { "echo x \\\">\\\" foo.bar", 1 },
00404         { "echo x \\\"<\\\" foo.bar", 1 }
00405     };
00406     int i;
00407     for ( i = 0; i < sizeof(tests)/sizeof(*tests); ++i)
00408     {
00409         assert( use_bat_file( tests[i].command ) == tests[i].result );
00410     }
00411 
00412     {
00413         char* long_command = malloc(MAXLINE + 10);
00414         assert( long_command != 0 );
00415         memset( long_command, 'x', MAXLINE + 9 );
00416         long_command[MAXLINE + 9] = 0;
00417         assert( use_bat_file( long_command ) );
00418         free( long_command );
00419     }
00420 #endif 
00421 }
00422 
00423 // SVA - handle temp dirs with spaces in the path
00424 static const char *getTempDir(void)
00425 {
00426     static char tempPath[_MAX_PATH];
00427     static char *pTempPath=NULL;
00428 
00429     if(pTempPath == NULL)
00430     {
00431         char *p;
00432 
00433         p = getenv("TEMP");
00434         if(p == NULL)
00435         {
00436             p = getenv("TMP");
00437         }
00438         if(p == NULL)
00439         {
00440             pTempPath = "\\temp";
00441         }
00442         else
00443         {
00444             GetShortPathName(p, tempPath, _MAX_PATH);
00445             pTempPath = tempPath;
00446         }
00447     }
00448     return pTempPath;
00449 }
00450 
00451 /*
00452  * execcmd() - launch an async command execution
00453  */
00454 
00455 void
00456 execcmd( 
00457         char *string,
00458         void (*func)( void *closure, int status ),
00459         void *closure,
00460         LIST *shell )
00461 {
00462     int pid;
00463     int slot;
00464     int raw_cmd = 0 ;
00465     char *argv_static[ MAXARGC + 1 ];   /* +1 for NULL */
00466     char **argv = argv_static;
00467     char *p;
00468 
00469     /* Check to see if we need to hack around the line-length limitation. */
00470     /* Look for a JAMSHELL setting of "%", indicating that the command
00471      * should be invoked directly */
00472     if ( shell && !strcmp(shell->string,"%") && !list_next(shell) )
00473     {
00474         raw_cmd = 1;
00475         shell = 0;
00476     }
00477 
00478     if ( !is_win95_defined )
00479         set_is_win95();
00480           
00481     /* Find a slot in the running commands table for this one. */
00482     if ( is_win95 )
00483     {
00484         /* only synchronous spans are supported on Windows 95/98 */
00485         slot = 0;
00486     }
00487     else
00488     {
00489         for( slot = 0; slot < MAXJOBS; slot++ )
00490             if( !cmdtab[ slot ].pid )
00491                 break;
00492     }
00493     if( slot == MAXJOBS )
00494     {
00495         printf( "no slots for child!\n" );
00496         exit( EXITBAD );
00497     }
00498   
00499     if( !cmdtab[ slot ].tempfile )
00500     {
00501         const char *tempdir;
00502         DWORD procID;
00503 
00504         tempdir = getTempDir();
00505   
00506         // SVA - allocate 64 other just to be safe
00507         cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 64 );
00508   
00509         procID = GetCurrentProcessId();
00510   
00511         sprintf( cmdtab[ slot ].tempfile, "%s\\jam%d-%02d.bat", 
00512                  tempdir, procID, slot );               
00513     }
00514 
00515     /* Trim leading, ending white space */
00516 
00517     while( isspace( *string ) )
00518         ++string;
00519 
00520     /* If multi line, or too long, or JAMSHELL is set, write to bat file. */
00521     /* Otherwise, exec directly. */
00522     /* Frankly, if it is a single long line I don't think the */
00523     /* command interpreter will do any better -- it will fail. */
00524 
00525     if( shell || !raw_cmd // && use_bat_file( string )
00526         )
00527     {
00528         FILE *f;
00529 
00530         /* Write command to bat file. */
00531 
00532         f = fopen( cmdtab[ slot ].tempfile, "w" );
00533         fputs( string, f );
00534         fclose( f );
00535 
00536         string = cmdtab[ slot ].tempfile;
00537         
00538         if( DEBUG_EXECCMD )
00539         {
00540             if (shell)
00541                 printf("using user-specified shell: %s", shell->string);
00542             else
00543                 printf("Executing through .bat file\n");
00544         }
00545     }
00546     else if( DEBUG_EXECCMD )
00547     {
00548         printf("Executing raw command directly\n");
00549     }
00550 
00551     /* Forumulate argv */
00552     /* If shell was defined, be prepared for % and ! subs. */
00553     /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */
00554 
00555     if( shell )
00556     {
00557         int i;
00558         char jobno[4];
00559         int gotpercent = 0;
00560 
00561         sprintf( jobno, "%d", slot + 1 );
00562 
00563         for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) )
00564         {
00565             switch( shell->string[0] )
00566             {
00567             case '%':   argv[i] = string; gotpercent++; break;
00568             case '!':   argv[i] = jobno; break;
00569             default:    argv[i] = shell->string;
00570             }
00571             if( DEBUG_EXECCMD )
00572                 printf( "argv[%d] = '%s'\n", i, argv[i] );
00573         }
00574 
00575         if( !gotpercent )
00576             argv[i++] = string;
00577 
00578         argv[i] = 0;
00579     }
00580     else if (raw_cmd)
00581     {
00582         int ignored;
00583         argv = string_to_args(string, &ignored);
00584     }
00585     else
00586     {
00587         /* don't worry, this is ignored on Win95/98, see later.. */
00588         argv[0] = "cmd.exe";
00589         argv[1] = "/Q/C";               /* anything more is non-portable */
00590         argv[2] = string;
00591         argv[3] = 0;
00592     }
00593 
00594     /* Catch interrupts whenever commands are running. */
00595 
00596     if( !cmdsrunning++ )
00597         istat = signal( SIGINT, onintr );
00598 
00599     /* Start the command */
00600 
00601     /* on Win95, we only do a synchronous call */
00602     if ( is_win95 )
00603     {
00604         static const char* hard_coded[] =
00605             {
00606                 "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir",
00607                 "ren", "rename", "move", 0
00608             };
00609           
00610         const char**  keyword;
00611         int           len, spawn = 1;
00612         int           result;
00613           
00614         for ( keyword = hard_coded; keyword[0]; keyword++ )
00615         {
00616             len = strlen( keyword[0] );
00617             if ( strnicmp( string, keyword[0], len ) == 0 &&
00618                  !isalnum(string[len]) )
00619             {
00620                 /* this is one of the hard coded symbols, use 'system' to run */
00621                 /* them.. except for "del"/"erase"                            */
00622                 if ( keyword - hard_coded < 2 )
00623                     result = process_del( string );
00624                 else
00625                     result = system( string );
00626 
00627                 spawn  = 0;
00628                 break;
00629             }
00630         }
00631           
00632         if (spawn)
00633         {
00634             char**  args;
00635             int     num_args;
00636             
00637             /* convert the string into an array of arguments */
00638             /* we need to take care of double quotes !!      */
00639             args = string_to_args( string, &num_args );
00640             if ( args )
00641             {
00642 #if 0
00643                 char** arg;
00644                 fprintf( stderr, "%s: ", args[0] );
00645                 arg = args+1;
00646                 while ( arg[0] )
00647                 {
00648                     fprintf( stderr, " {%s}", arg[0] );
00649                     arg++;
00650                 }
00651                 fprintf( stderr, "\n" );
00652 #endif              
00653                 result = spawnvp( P_WAIT, args[0], args );
00654                 free_args( args );
00655             }
00656             else
00657                 result = 1;
00658         }
00659         func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK );
00660         return;
00661     }
00662 
00663     if( DEBUG_EXECCMD )
00664     {
00665         char **argp = argv;
00666 
00667         printf("Executing command");
00668         while(*argp != 0)
00669         {
00670             printf(" [%s]", *argp);
00671             argp++;
00672         }
00673         printf("\n");
00674     }
00675 
00676     /* the rest is for Windows NT only */
00677     if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 )
00678     {
00679         perror( "spawn" );
00680         exit( EXITBAD );
00681     }
00682     /* Save the operation for execwait() to find. */
00683 
00684     cmdtab[ slot ].pid = pid;
00685     cmdtab[ slot ].func = func;
00686     cmdtab[ slot ].closure = closure;
00687 
00688     /* Wait until we're under the limit of concurrent commands. */
00689     /* Don't trust globs.jobs alone.                            */
00690 
00691     while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs )
00692         if( !execwait() )
00693             break;
00694     
00695     if (argv != argv_static)
00696     {
00697         free_args(argv);
00698     }
00699 }
00700 
00701 /*
00702  * execwait() - wait and drive at most one execution completion
00703  */
00704 
00705 int
00706 execwait()
00707 {
00708         int i;
00709         int status, w;
00710         int rstat;
00711 
00712         /* Handle naive make1() which doesn't know if cmds are running. */
00713 
00714         if( !cmdsrunning )
00715             return 0;
00716 
00717         if ( is_win95 )
00718           return 0;
00719           
00720         /* Pick up process pid and status */
00721     
00722         while( ( w = wait( &status ) ) == -1 && errno == EINTR )
00723                 ;
00724 
00725         if( w == -1 )
00726         {
00727             printf( "child process(es) lost!\n" );
00728             perror("wait");
00729             exit( EXITBAD );
00730         }
00731 
00732         /* Find the process in the cmdtab. */
00733 
00734         for( i = 0; i < MAXJOBS; i++ )
00735             if( w == cmdtab[ i ].pid )
00736                 break;
00737 
00738         if( i == MAXJOBS )
00739         {
00740             printf( "waif child found!\n" );
00741             exit( EXITBAD );
00742         }
00743 
00744         /* Drive the completion */
00745 
00746         if( !--cmdsrunning )
00747             signal( SIGINT, istat );
00748 
00749         if( intr )
00750             rstat = EXEC_CMD_INTR;
00751         else if( w == -1 || status != 0 )
00752             rstat = EXEC_CMD_FAIL;
00753         else
00754             rstat = EXEC_CMD_OK;
00755 
00756         cmdtab[ i ].pid = 0;
00757         // SVA don't leak temp files
00758         if(cmdtab[i].tempfile != NULL)
00759         {
00760             free(cmdtab[i].tempfile);
00761             cmdtab[i].tempfile = NULL;
00762         }
00763         (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat );
00764 
00765         return 1;
00766 }
00767 
00768 # if !defined( __BORLANDC__ )
00769 
00770 static int
00771 my_wait( int *status )
00772 {
00773         int i, num_active = 0;
00774         DWORD exitcode, waitcode;
00775         static HANDLE *active_handles = 0;
00776 
00777         if (!active_handles)
00778             active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) );
00779 
00780         /* first see if any non-waited-for processes are dead,
00781          * and return if so.
00782          */
00783         for ( i = 0; i < globs.jobs; i++ ) {
00784             if ( cmdtab[i].pid ) {
00785                 if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) {
00786                     if ( exitcode == STILL_ACTIVE )
00787                         active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
00788                     else {
00789                         CloseHandle((HANDLE)cmdtab[i].pid);
00790                         *status = (int)((exitcode & 0xff) << 8);
00791                         return cmdtab[i].pid;
00792                     }
00793                 }
00794                 else
00795                     goto FAILED;
00796             }
00797         }
00798 
00799         /* if a child exists, wait for it to die */
00800         if ( !num_active ) {
00801             errno = ECHILD;
00802             return -1;
00803         }
00804         waitcode = WaitForMultipleObjects( num_active,
00805                                            active_handles,
00806                                            FALSE,
00807                                            INFINITE );
00808         if ( waitcode != WAIT_FAILED ) {
00809             if ( waitcode >= WAIT_ABANDONED_0
00810                 && waitcode < WAIT_ABANDONED_0 + num_active )
00811                 i = waitcode - WAIT_ABANDONED_0;
00812             else
00813                 i = waitcode - WAIT_OBJECT_0;
00814             if ( GetExitCodeProcess(active_handles[i], &exitcode) ) {
00815                 CloseHandle(active_handles[i]);
00816                 *status = (int)((exitcode & 0xff) << 8);
00817                 return (int)active_handles[i];
00818             }
00819         }
00820 
00821 FAILED:
00822         errno = GetLastError();
00823         return -1;
00824     
00825 }
00826 
00827 # endif /* !__BORLANDC__ */
00828 
00829 # endif /* USE_EXECNT */

Generated on Mon Nov 8 17:07:32 2004 for MPT by  doxygen 1.3.9.1