sl@0: /* sl@0: * tclAppInit.c -- sl@0: * sl@0: * Provides a default version of the main program and Tcl_AppInit sl@0: * procedure for Tcl applications (without Tk). Note that this sl@0: * program must be built in Win32 console mode to work properly. sl@0: * sl@0: * Copyright (c) 1996-1997 by Sun Microsystems, Inc. sl@0: * Copyright (c) 1998-1999 by Scriptics Corporation. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclAppInit.c,v 1.11.2.3 2007/03/19 17:06:26 dgp Exp $ sl@0: */ sl@0: sl@0: #include "tcl.h" sl@0: #include sl@0: #include sl@0: sl@0: #ifdef TCL_TEST sl@0: extern int Procbodytest_Init _ANSI_ARGS_((Tcl_Interp *interp)); sl@0: extern int Procbodytest_SafeInit _ANSI_ARGS_((Tcl_Interp *interp)); sl@0: extern int Tcltest_Init _ANSI_ARGS_((Tcl_Interp *interp)); sl@0: extern int TclObjTest_Init _ANSI_ARGS_((Tcl_Interp *interp)); sl@0: #ifdef TCL_THREADS sl@0: extern int TclThread_Init _ANSI_ARGS_((Tcl_Interp *interp)); sl@0: #endif sl@0: #endif /* TCL_TEST */ sl@0: sl@0: static void setargv _ANSI_ARGS_((int *argcPtr, char ***argvPtr)); sl@0: static BOOL __stdcall sigHandler (DWORD fdwCtrlType); sl@0: static Tcl_AsyncProc asyncExit; sl@0: static void AppInitExitHandler(ClientData clientData); sl@0: sl@0: static char ** argvSave = NULL; sl@0: static Tcl_AsyncHandler exitToken = NULL; sl@0: static DWORD exitErrorCode = 0; sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * main -- sl@0: * sl@0: * This is the main program for the application. sl@0: * sl@0: * Results: sl@0: * None: Tcl_Main never returns here, so this procedure never sl@0: * returns either. sl@0: * sl@0: * Side effects: sl@0: * Whatever the application does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: main(argc, argv) sl@0: int argc; /* Number of command-line arguments. */ sl@0: char **argv; /* Values of command-line arguments. */ sl@0: { sl@0: /* sl@0: * The following #if block allows you to change the AppInit sl@0: * function by using a #define of TCL_LOCAL_APPINIT instead sl@0: * of rewriting this entire file. The #if checks for that sl@0: * #define and uses Tcl_AppInit if it doesn't exist. sl@0: */ sl@0: sl@0: #ifndef TCL_LOCAL_APPINIT sl@0: #define TCL_LOCAL_APPINIT Tcl_AppInit sl@0: #endif sl@0: extern int TCL_LOCAL_APPINIT _ANSI_ARGS_((Tcl_Interp *interp)); sl@0: sl@0: /* sl@0: * The following #if block allows you to change how Tcl finds the startup sl@0: * script, prime the library or encoding paths, fiddle with the argv, sl@0: * etc., without needing to rewrite Tcl_Main() sl@0: */ sl@0: sl@0: #ifdef TCL_LOCAL_MAIN_HOOK sl@0: extern int TCL_LOCAL_MAIN_HOOK _ANSI_ARGS_((int *argc, char ***argv)); sl@0: #endif sl@0: sl@0: char buffer[MAX_PATH +1]; sl@0: char *p; sl@0: /* sl@0: * Set up the default locale to be standard "C" locale so parsing sl@0: * is performed correctly. sl@0: */ sl@0: sl@0: setlocale(LC_ALL, "C"); sl@0: setargv(&argc, &argv); sl@0: sl@0: /* sl@0: * Save this for later, so we can free it. sl@0: */ sl@0: argvSave = argv; sl@0: sl@0: /* sl@0: * Replace argv[0] with full pathname of executable, and forward sl@0: * slashes substituted for backslashes. sl@0: */ sl@0: sl@0: GetModuleFileName(NULL, buffer, sizeof(buffer)); sl@0: argv[0] = buffer; sl@0: for (p = buffer; *p != '\0'; p++) { sl@0: if (*p == '\\') { sl@0: *p = '/'; sl@0: } sl@0: } sl@0: sl@0: #ifdef TCL_LOCAL_MAIN_HOOK sl@0: TCL_LOCAL_MAIN_HOOK(&argc, &argv); sl@0: #endif sl@0: sl@0: Tcl_Main(argc, argv, TCL_LOCAL_APPINIT); sl@0: sl@0: return 0; /* Needed only to prevent compiler warning. */ sl@0: } sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_AppInit -- sl@0: * sl@0: * This procedure performs application-specific initialization. sl@0: * Most applications, especially those that incorporate additional sl@0: * packages, will have their own version of this procedure. sl@0: * sl@0: * Results: sl@0: * Returns a standard Tcl completion code, and leaves an error sl@0: * message in the interp's result if an error occurs. sl@0: * sl@0: * Side effects: sl@0: * Depends on the startup script. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: Tcl_AppInit(interp) sl@0: Tcl_Interp *interp; /* Interpreter for application. */ sl@0: { sl@0: if (Tcl_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: * Install a signal handler to the win32 console tclsh is running in. sl@0: */ sl@0: SetConsoleCtrlHandler(sigHandler, TRUE); sl@0: exitToken = Tcl_AsyncCreate(asyncExit, NULL); sl@0: sl@0: /* sl@0: * This exit handler will be used to free the sl@0: * resources allocated in this file. sl@0: */ sl@0: Tcl_CreateExitHandler(AppInitExitHandler, NULL); sl@0: sl@0: #ifdef TCL_TEST sl@0: if (Tcltest_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: Tcl_StaticPackage(interp, "Tcltest", Tcltest_Init, sl@0: (Tcl_PackageInitProc *) NULL); sl@0: if (TclObjTest_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: #ifdef TCL_THREADS sl@0: if (TclThread_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: #endif sl@0: if (Procbodytest_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: Tcl_StaticPackage(interp, "procbodytest", Procbodytest_Init, sl@0: Procbodytest_SafeInit); sl@0: #endif /* TCL_TEST */ sl@0: sl@0: #if defined(STATIC_BUILD) && defined(TCL_USE_STATIC_PACKAGES) sl@0: { sl@0: extern Tcl_PackageInitProc Registry_Init; sl@0: extern Tcl_PackageInitProc Dde_Init; sl@0: sl@0: if (Registry_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: Tcl_StaticPackage(interp, "registry", Registry_Init, NULL); sl@0: sl@0: if (Dde_Init(interp) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: Tcl_StaticPackage(interp, "dde", Dde_Init, NULL); sl@0: } sl@0: #endif sl@0: sl@0: /* sl@0: * Call the init procedures for included packages. Each call should sl@0: * look like this: sl@0: * sl@0: * if (Mod_Init(interp) == TCL_ERROR) { sl@0: * return TCL_ERROR; sl@0: * } sl@0: * sl@0: * where "Mod" is the name of the module. sl@0: */ sl@0: sl@0: /* sl@0: * Call Tcl_CreateCommand for application-specific commands, if sl@0: * they weren't already created by the init procedures called above. sl@0: */ sl@0: sl@0: /* sl@0: * Specify a user-specific startup file to invoke if the application sl@0: * is run interactively. Typically the startup file is "~/.apprc" sl@0: * where "app" is the name of the application. If this line is deleted sl@0: * then no user-specific startup file will be run under any conditions. sl@0: */ sl@0: sl@0: Tcl_SetVar(interp, "tcl_rcFileName", "~/tclshrc.tcl", TCL_GLOBAL_ONLY); sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * AppInitExitHandler -- sl@0: * sl@0: * This function is called to cleanup the app init resources before sl@0: * Tcl is unloaded. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Frees the saved argv and deletes the async exit handler. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: AppInitExitHandler( sl@0: ClientData clientData) sl@0: { sl@0: if (argvSave != NULL) { sl@0: ckfree((char *)argvSave); sl@0: argvSave = NULL; sl@0: } sl@0: sl@0: if (exitToken != NULL) { sl@0: /* sl@0: * This should be safe to do even if we sl@0: * are in an async exit right now. sl@0: */ sl@0: Tcl_AsyncDelete(exitToken); sl@0: exitToken = NULL; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------------------------- sl@0: * sl@0: * setargv -- sl@0: * sl@0: * Parse the Windows command line string into argc/argv. Done here sl@0: * because we don't trust the builtin argument parser in crt0. sl@0: * Windows applications are responsible for breaking their command sl@0: * line into arguments. sl@0: * sl@0: * 2N backslashes + quote -> N backslashes + begin quoted string sl@0: * 2N + 1 backslashes + quote -> literal sl@0: * N backslashes + non-quote -> literal sl@0: * quote + quote in a quoted string -> single quote sl@0: * quote + quote not in quoted string -> empty string sl@0: * quote -> begin quoted string sl@0: * sl@0: * Results: sl@0: * Fills argcPtr with the number of arguments and argvPtr with the sl@0: * array of arguments. sl@0: * sl@0: * Side effects: sl@0: * Memory allocated. sl@0: * sl@0: *-------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: setargv(argcPtr, argvPtr) sl@0: int *argcPtr; /* Filled with number of argument strings. */ sl@0: char ***argvPtr; /* Filled with argument strings (malloc'd). */ sl@0: { sl@0: char *cmdLine, *p, *arg, *argSpace; sl@0: char **argv; sl@0: int argc, size, inquote, copy, slashes; sl@0: sl@0: cmdLine = GetCommandLine(); /* INTL: BUG */ sl@0: sl@0: /* sl@0: * Precompute an overly pessimistic guess at the number of arguments sl@0: * in the command line by counting non-space spans. sl@0: */ sl@0: sl@0: size = 2; sl@0: for (p = cmdLine; *p != '\0'; p++) { sl@0: if ((*p == ' ') || (*p == '\t')) { /* INTL: ISO space. */ sl@0: size++; sl@0: while ((*p == ' ') || (*p == '\t')) { /* INTL: ISO space. */ sl@0: p++; sl@0: } sl@0: if (*p == '\0') { sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: argSpace = (char *) ckalloc( sl@0: (unsigned) (size * sizeof(char *) + strlen(cmdLine) + 1)); sl@0: argv = (char **) argSpace; sl@0: argSpace += size * sizeof(char *); sl@0: size--; sl@0: sl@0: p = cmdLine; sl@0: for (argc = 0; argc < size; argc++) { sl@0: argv[argc] = arg = argSpace; sl@0: while ((*p == ' ') || (*p == '\t')) { /* INTL: ISO space. */ sl@0: p++; sl@0: } sl@0: if (*p == '\0') { sl@0: break; sl@0: } sl@0: sl@0: inquote = 0; sl@0: slashes = 0; sl@0: while (1) { sl@0: copy = 1; sl@0: while (*p == '\\') { sl@0: slashes++; sl@0: p++; sl@0: } sl@0: if (*p == '"') { sl@0: if ((slashes & 1) == 0) { sl@0: copy = 0; sl@0: if ((inquote) && (p[1] == '"')) { sl@0: p++; sl@0: copy = 1; sl@0: } else { sl@0: inquote = !inquote; sl@0: } sl@0: } sl@0: slashes >>= 1; sl@0: } sl@0: sl@0: while (slashes) { sl@0: *arg = '\\'; sl@0: arg++; sl@0: slashes--; sl@0: } sl@0: sl@0: if ((*p == '\0') sl@0: || (!inquote && ((*p == ' ') || (*p == '\t')))) { /* INTL: ISO space. */ sl@0: break; sl@0: } sl@0: if (copy != 0) { sl@0: *arg = *p; sl@0: arg++; sl@0: } sl@0: p++; sl@0: } sl@0: *arg = '\0'; sl@0: argSpace = arg + 1; sl@0: } sl@0: argv[argc] = NULL; sl@0: sl@0: *argcPtr = argc; sl@0: *argvPtr = argv; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * asyncExit -- sl@0: * sl@0: * The AsyncProc for the exitToken. sl@0: * sl@0: * Results: sl@0: * doesn't actually return. sl@0: * sl@0: * Side effects: sl@0: * tclsh cleanly exits. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: asyncExit (ClientData clientData, Tcl_Interp *interp, int code) sl@0: { sl@0: Tcl_Exit((int)exitErrorCode); sl@0: sl@0: /* NOTREACHED */ sl@0: return code; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * sigHandler -- sl@0: * sl@0: * Signal handler for the Win32 OS. Catches Ctrl+C, Ctrl+Break and sl@0: * other exits. This is needed so tclsh can do it's real clean-up sl@0: * and not an unclean crash terminate. sl@0: * sl@0: * Results: sl@0: * TRUE. sl@0: * sl@0: * Side effects: sl@0: * Effects the way the app exits from a signal. This is an sl@0: * operating system supplied thread and unsafe to call ANY sl@0: * Tcl commands except for Tcl_AsyncMark. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: BOOL __stdcall sl@0: sigHandler(DWORD fdwCtrlType) sl@0: { sl@0: HANDLE hStdIn; sl@0: sl@0: if (!exitToken) { sl@0: /* Async token must have been destroyed, punt gracefully. */ sl@0: return FALSE; sl@0: } sl@0: sl@0: /* sl@0: * If Tcl is currently executing some bytecode or in the eventloop, sl@0: * this will cause Tcl to enter asyncExit at the next command sl@0: * boundry. sl@0: */ sl@0: exitErrorCode = fdwCtrlType; sl@0: Tcl_AsyncMark(exitToken); sl@0: sl@0: /* sl@0: * This will cause Tcl_Gets in Tcl_Main() to drop-out with an sl@0: * should it be blocked on input and our Tcl_AsyncMark didn't grab sl@0: * the attention of the interpreter. sl@0: */ sl@0: hStdIn = GetStdHandle(STD_INPUT_HANDLE); sl@0: if (hStdIn) { sl@0: CloseHandle(hStdIn); sl@0: } sl@0: sl@0: /* indicate to the OS not to call the default terminator */ sl@0: return TRUE; sl@0: }