/* FILE: oc.cc                   -*-Mode: c++-*-
 *
 *      The OOMMF Core extension.
 *
 *      This extension provides a set of C++ classes and functions and
 * Tcl commands of general utility to all OOMMF applications.  This 
 * includes portability support for managing the differences among 
 * computing platforms, compilers, and Tcl/Tk library versions.  
 *
 * Because there are platform and compiler differences in which C++ function
 * serves as the main entry point to an application, and what arguments
 * it receives from the operating system, those entry points are provided
 * as part of this extension, so the portability burden is borne here and
 * is not passed to application writers.
 * 
 * NOTICE: Please see the file ../../LICENSE
 *
 * Last modified on: $Date: 2003/11/20 22:41:34 $
 * Last modified by: $Author: dgp $
 */

/* Header files for standard libraries */
#include <string.h>
#include <stdlib.h>
#include <signal.h>

/* Header file for this extension */
#include "oc.h"

/* Implementation-defined limits (ANSI C) */
#include <limits.h>
#include <float.h>

OC_USE_STD_NAMESPACE;
/* End includes */     

/*
 * Dummy function calls to force expression evaluation.
 * Used to work around some broken compiler optimizations
 * (notably egcs).
 */
double Oc_Nop(double x) { return x; }

/*
 * The global interpreter of the application.
 */

static Tcl_Interp *globalInterp = (Tcl_Interp *)NULL;

/*
 * Do we load the Tk extension? (Default = yes)
 */
static BOOL use_tk = 1;

/*
 * Do we parse the command line? 
 */
static BOOL parseCommandLine = 0;

/*
 * The following static strings contain Tcl scripts which are evaluated
 * to provide a small library of routines needed to accomplish the task
 * of finding the initialization Tcl scripts for the Oc extension.
 *
 * NOTE: There must be several separate strings because some compilers
 * place a rather small limit on the maximum length of a string literal.
 */

// Fix for bug in Tk_Init in Tk 4.2
static char patchScript0[] =
"if {[string match 7.6 $tcl_version] && ![info exists tk_library]} {\n\
   set tk_library [file join [file dirname $tcl_library] tk4.2]\n\
}";

// NOMAC: Assumes '..' means 'parent directory'
static char initScript0[] =
"proc Oc_DirectPathname { pathname } {\n\
    global Oc_DirectPathnameCache\n\
    set canCdTo [file dirname $pathname]\n\
    set rest [file tail $pathname]\n\
    switch -exact -- $rest {. - .. {\n\
        set canCdTo [file join $canCdTo $rest]\n\
        set $rest {}\n\
    }}\n\
    if {[string match absolute [file pathtype $canCdTo]]} {\n\
        set index $canCdTo\n\
    } else {\n\
        set index [file join [pwd] $canCdTo]\n\
    }\n\
    if {[info exists Oc_DirectPathnameCache($index)]} {\n\
        return [file join $Oc_DirectPathnameCache($index) $rest]\n\
    }\n\
    if {[catch {set savedir [pwd]} msg]} {\n\
        return -code error \"Can't determine pathname for\n\
\t$pathname:\n\t$msg\"\n\
    }\n\
    while {[catch {cd $canCdTo}]} {\n\
        switch -exact -- [file tail $canCdTo] {\n\
            {} {set Oc_DirectPathnameCache($index) $canCdTo\n\
                return [file join $Oc_DirectPathnameCache($index) $rest]}\n\
            . {} .. {\n\
                set Oc_DirectPathnameCache($index) [file dirname \\\n\
                        [Oc_DirectPathname [file dirname $canCdTo]]]\n\
                return [file join $Oc_DirectPathnameCache($index) $rest]}\n\
            default {set rest [file join [file tail $canCdTo] $rest]}\n\
        }\n\
        set canCdTo [file dirname $canCdTo]\n\
        set index [file dirname $index]\n\
    }\n\
    catch {set Oc_DirectPathnameCache($index) [pwd]}\n\
    cd $savedir\n\
    if {![info exists Oc_DirectPathnameCache($index)]} {\n\
        set Oc_DirectPathnameCache($index) [Oc_DirectPathname $canCdTo]\n\
    }\n\
    return [file join $Oc_DirectPathnameCache($index) $rest]\n\
}";

static char initScript1[] =
"proc Oc_AppendUniqueReadableDirectory {listref dir} {\n\
    upvar $listref list\n\
    if {[file isdirectory $dir] && [file readable $dir]} {\n\
        set dir [Oc_DirectPathname $dir]\n\
        if {[lsearch -exact $list $dir] < 0} {\n\
            lappend list $dir\n\
        }\n\
    }\n\
}";

static char initScript2[] =
"proc Oc_ScriptPath {Pkg ver} {\n\
    global env \n\
    set dirs {}\n\
    set envVar [string toupper $Pkg]_LIBRARY\n\
    if [info exists env($envVar)] {\n\
        Oc_AppendUniqueReadableDirectory dirs $env($envVar)\n\
    }\n\
    set pkg [string tolower $Pkg]\n\
    set tclVar ${pkg}_library\n\
    if [info exists $tclVar] {\n\
        Oc_AppendUniqueReadableDirectory dirs [set $tclVar]\n\
    }\n\
    set execName [Oc_DirectPathname [info nameofexecutable]] \n\
    set parDir [file dirname [file dirname $execName]]\n\
    set ggparDir [file dirname [file dirname $parDir]]\n\
    Oc_AppendUniqueReadableDirectory dirs \\\n\
            [file join $ggparDir ext $pkg$ver]\n\
    Oc_AppendUniqueReadableDirectory dirs \\\n\
            [file join $ggparDir ext $pkg]\n\
    Oc_AppendUniqueReadableDirectory dirs $parDir\n\
    Oc_AppendUniqueReadableDirectory dirs [file join $parDir base]\n\
    return $dirs\n\
 }";

static char initScript3[] =
"proc Oc_InitScript {Pkg ver} {\n\
    set pkg [string tolower $Pkg]\n\
    global ${pkg}_library\n\
    set dirs [Oc_ScriptPath $Pkg $ver]\n\
    foreach i $dirs {\n\
        set ${pkg}_library $i\n\
        set script [file join $i $pkg.tcl]\n\
        if {[file isfile $script] && [file readable $script]} {\n\
            uplevel #0 [list source $script]\n\
            return\n\
        }\n\
    }\n\
    set msg \"\nCan't find the '$Pkg' initialization script, $pkg.tcl,\"\n\
    append msg \"\nin the following directories:\n\n\"\n\
    append msg \"[join $dirs {\n}]\n\n\"\n\
    append msg \"$Pkg probably hasn't been \"\n\
    append msg \"installed properly.\n\n\"\n\
    append msg \"Either (re-)install $Pkg, \"\n\
    append msg \"or set the environment variable \"\n\
    append msg \"[string toupper $Pkg]_LIBRARY\nto the name \"\n\
    append msg \"of a directory containing $pkg.tcl.\"\n\
    return -code error $msg\n\
}";

int
Oc_InitScript(Tcl_Interp *interp, const char *pkg, const char *version)
{
    Tcl_DString buf;
    Oc_AutoBuf ab;
    int code;

    Tcl_DStringInit(&buf);
    Tcl_DStringAppendElement(&buf, ab("Oc_InitScript"));
    Tcl_DStringAppendElement(&buf, ab(pkg));
    Tcl_DStringAppendElement(&buf, ab(version));
    code = Tcl_GlobalEval(interp, Tcl_DStringValue(&buf));
    Tcl_DStringFree(&buf);
    return code;
}

int
Oc_RegisterCommand(Tcl_Interp* interp, const char* name,
Tcl_CmdProc* cmd)
{
  Oc_AutoBuf ab;
  if(Tcl_CreateCommand(interp,ab(name),cmd,NULL,NULL)==NULL) {
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, "Unable to register command ->",
                     name, "<- with Tcl interpreter", (char *) NULL);
    return TCL_ERROR;
  }
  return TCL_OK;
}

#ifndef SIGHUP
#define SIGHUP 1
#endif
#ifndef SIGTTIN
#define SIGTTIN 21
#endif
#ifndef SIGTTOU
#define SIGTTOU 22
#endif

extern "C" void DisableStdio(int);
void DisableStdio(int)
{ // Redirects stdio channels to NULL
  Oc_AutoBuf ab;

  // Get name of NULL device
  Tcl_Eval(globalInterp,
           ab("[Oc_Config RunPlatform] GetValue path_device_null"));
  Oc_AutoBuf nulin(Tcl_GetStringResult(globalInterp));
#ifndef DEBUG_DISABLESTDIO
  Oc_AutoBuf nulout(Tcl_GetStringResult(globalInterp));
#else
  char buf[512];
  sprintf(buf,"/tmp/dummynul-%d",getpid());
  Oc_AutoBuf nulout(buf);
#endif // DEBUG_DISABLESTDIO

  // Reset C FILE*'s
  freopen((char*)nulin,"r",stdin);
  freopen((char*)nulout,"a",stdout);
  freopen((char*)nulout,"a",stderr);

#ifdef DEBUG_DISABLESTDIO
  setvbuf(stdout,NULL,_IONBF,0);
  setvbuf(stderr,NULL,_IONBF,0);
  fprintf(stderr,"nul opened by %d\n",getpid());
#endif // DEBUG_DISABLESTDIO

  // Reset Tcl channels
  Tcl_Channel nulchanin =
    Tcl_OpenFileChannel(NULL,(char*)nulin,ab("r"),0644);
  Tcl_Channel nulchanout =
    Tcl_OpenFileChannel(NULL,(char*)nulout,ab("a+"),0644);
  Tcl_SetStdChannel(nulchanin,TCL_STDIN);
  Tcl_SetStdChannel(nulchanout,TCL_STDOUT);
  Tcl_SetStdChannel(nulchanout,TCL_STDERR);
  Tcl_RegisterChannel(globalInterp,nulchanin);
  Tcl_RegisterChannel(globalInterp,nulchanout);
#ifdef DEBUG_DISABLESTDIO
  Oc_AutoBuf ab1,ab2;
  Tcl_SetChannelOption(NULL,nulchanout,ab1("-buffering"),ab2("none"));
#endif // DEBUG_DISABLESTDIO

  // Disable future SIGHUP, SIGTIN and SIGTOU signals
#if 1 && (SYSTEM_TYPE==WINDOWS)
  signal(SIGBREAK,SIG_IGN);
  signal(SIGABRT,SIG_IGN);
#else
  signal(SIGHUP,SIG_IGN);
  signal(SIGTTIN,SIG_IGN);
  signal(SIGTTOU,SIG_IGN);
#endif
}

static int
IgnoreSignal(int signo)
{
  //  1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
  //  5) SIGTRAP      6) SIGIOT       7) SIGEMT       8) SIGFPE
  //  9) SIGKILL     10) SIGBUS      11) SIGSEGV     12) SIGSYS
  // 13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGURG
  // 17) SIGSTOP     18) SIGTSTP     19) SIGCONT     20) SIGCHLD
  // 21) SIGTTIN     22) SIGTTOU     23) SIGIO       24) SIGXCPU
  // 25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
  // 29) SIGPWR      30) SIGUSR1     31) SIGUSR2
  //  NOTE: The number<->name matching may be system dependent, so it
  //    is best to use the symbolic names as defined in /usr/signal.h
  // Under Windows, only the following subset is apparently available:
  //   SIGINT, SIGILL, SIGFPE, SIGSEGV, SIGTERM, SIGBREAK, SIGABRT
  // Unconfirmed reports claim Windows will crash if signal() is sent
  // a value outside this set.  Note that signal() will correctly
  // return a TCL_ERROR if the value is outside the known set of
  // signals.
#if 1 && (SYSTEM_TYPE==WINDOWS)
  if(signo!=SIGINT && signo!=SIGILL && signo!=SIGFPE
     && signo!=SIGSEGV && signo!=SIGTERM && signo!=SIGBREAK
     && signo!=SIGABRT)
    return TCL_ERROR;
#endif
  if(signo==SIGHUP || signo==SIGTTIN || signo==SIGTTOU) {
#ifdef __GNUC__
    if( (void*)signal(signo,(omf_sighandler)DisableStdio) == (void*)SIG_ERR )
#else
    if( signal(signo,(omf_sighandler)DisableStdio) == SIG_ERR )
#endif
      return TCL_ERROR;
  } else {
#ifdef __GNUC__
    if( (void*)signal(signo,SIG_IGN) == (void*)SIG_ERR )
#else
    if( signal(signo,SIG_IGN) == SIG_ERR )
#endif
      return TCL_ERROR;
  }
  /// The (void*) casts above are to work around a gcc-2.7.2.2 (and
  /// others?) bug that shows up on platforms with a non-ANSI signal(),
  /// e.g., SunOS.  In that circumstance, the return value from signal()
  /// is treated as void (*)(...) instead of void (*)(), i.e.,
  /// void (*)(void).
  ///
  /// UPDATE: dgp: 1999 May 19: Some compilers, notably Sun Workshop 5.0
  /// refuse the cast to void *.  So the casting above is now limited to
  /// those compilers which define __GNUC__ (presumably only gcc, etc.)
  return TCL_OK;
}

static int
Oc_IgnoreSignal(ClientData, Tcl_Interp *interp, int argc, char **argv) 
{
  Tcl_ResetResult(interp);
  if (argc != 2) {
    Tcl_AppendResult(interp, argv[0], " must be called with"
                     " 1 argument: signal", (char *) NULL);
    return TCL_ERROR;
  }
  return IgnoreSignal(atoi(argv[1]));
}

static int
LockChannel(Tcl_Interp *interp,const char* channelName,int writespec)
{
  // Returns 0 on success, 1 if file already locked,
  // -1 on GetChannel error, -2 on GetChannelHandle error
  Tcl_Channel chan=Tcl_GetChannel(interp,Oc_AutoBuf(channelName),NULL);
  if(chan==NULL) return -1;
  int direction=TCL_WRITABLE;
  if(!writespec) direction=TCL_READABLE;
#if SYSTEM_TYPE==UNIX
  int handle;
  if(Tcl_GetChannelHandle(chan,direction,(ClientData *)&handle)!=TCL_OK)
    return -2;
  struct flock lock;
  lock.l_type=F_WRLCK;
  if(!writespec) lock.l_type=F_RDLCK;
  lock.l_start=0;
  lock.l_whence=SEEK_SET;
  lock.l_len=0; // Lock entire file
  if(fcntl(handle,F_SETLK,&lock)==0) return 0; // Success!
  return 1;  // Lock already held
#endif // SYSTEM_TYPE==UNIX
#if SYSTEM_TYPE==WINDOWS
  HANDLE osfhandle;
  if(Tcl_GetChannelHandle(chan,direction,(ClientData *)&osfhandle)!=TCL_OK)
    return -2;
  int result=LockFile(osfhandle,0,0,1,0);
  if(result) return 0; // Success!
  return 1; // Lock already held
#endif // SYSTEM_TYPE==WINDOWS
}

static int
Oc_LockChannel(ClientData, Tcl_Interp *interp, int argc, char **argv)
{ // Warning: On Unix, locks are indexed by file+pid, so code like
  //          fd1 = open("myfile",...);
  //          LOCK(fd1);
  //          fd2 = open("myfile",...);
  //          LOCK(fd2);
  //          UNLOCK(fd2);
  //       unlocks fd1 as well.
  // WARNING: Closing a file releases all locks on the file, so
  //          fd1 = open("myfile","w");
  //          LOCK(fd1);
  //          fd2 = open("myfile","r");
  //          close(fd2);
  //       also unlocks fd1!!!  This is dangerous, and I don't see
  //       any way around it. :^( 
  //          See "Advanced Programming in the Unix Environment,"
  //       W.R. Stevens, Addison-Wesley (1993) p373, for more
  //       details. -mjd 29-July-1999
  // 
  //  The Windows locking mechanism behaves differently.  Only one write
  //  lock is permitted at any time on any file, and if one tries to
  //  re-lock an already locked file, the lock attempt will fail.  Also,
  //  unlocking a non-locked file fails.  Closing a file handle that was
  //  used to lock a file releases the lock, but closing a different
  //  file handle opened from the same file has no effect on the lock.
  //  NOTE: I could not determine a way to lock the entire file, so we
  //  just lock the first byte instead.  This should suffice for the
  //  cooperative locking scheme for which this routine is intended.
  //  -mjd, 30-July-1999

  Tcl_ResetResult(interp);
  if (argc != 3) {
    Tcl_AppendResult(interp, argv[0], " must be called with"
                     " 2 arguments: channel <r|w>", (char *) NULL);
    return TCL_ERROR;
  }
  const char* channelName=argv[1];
  int writespec=1;
  if(argv[2][0]=='r') writespec=0;
  int result=LockChannel(interp,channelName,writespec);
  if(result<0) {
    Tcl_AppendResult(interp,"LOCKING ERROR: ",(char *) NULL);
    switch(result)
      {
      case -1:
        Tcl_AppendResult(interp,"Bad channel name",(char *) NULL);
        break;
      case -2:
        Tcl_AppendResult(interp,"Bad read/write mode",(char *) NULL);
        break;
      default:
        Tcl_AppendResult(interp,"Bad lock",(char *) NULL);
        break;
      }
    return TCL_ERROR;
  }
  if(result==0) Tcl_AppendResult(interp,"1",(char *) NULL); // Success
  else          Tcl_AppendResult(interp,"0",(char *) NULL); // Failure
  return TCL_OK;
}

static int
UnlockChannel(Tcl_Interp *interp,const char* channelName,int writespec)
{
  // Returns 0 on success
  // -1 on GetChannel error, -2 on GetChannelHandle error
  // -3 on locking error
  Tcl_Channel chan=Tcl_GetChannel(interp,Oc_AutoBuf(channelName),NULL);
  if(chan==NULL) return -1;
  int direction=TCL_WRITABLE;
  if(!writespec) direction=TCL_READABLE;
#if SYSTEM_TYPE==UNIX
  int handle;
  if(Tcl_GetChannelHandle(chan,direction,(ClientData *)&handle)!=TCL_OK)
    return -2;
  struct flock lock;
  lock.l_type=F_UNLCK;
  lock.l_start=0;
  lock.l_whence=SEEK_SET;
  lock.l_len=0; // Entire file
  if(fcntl(handle,F_SETLK,&lock)==0) return 0; // Success!
  return -1;  // Error
#endif // SYSTEM_TYPE==UNIX
#if SYSTEM_TYPE==WINDOWS
  HANDLE osfhandle;
  if(Tcl_GetChannelHandle(chan,direction,(ClientData *)&osfhandle)!=TCL_OK)
    return -2;
  int result=UnlockFile(osfhandle,0,0,1,0);
  if(result) return 0; // Success!
  return 1; // Error; file probably not locked.
#endif // SYSTEM_TYPE==WINDOWS
}

static int
Oc_UnlockChannel(ClientData, Tcl_Interp *interp, int argc, char **argv)
{
  Tcl_ResetResult(interp);
  if (argc != 3) {
    Tcl_AppendResult(interp, argv[0], " must be called with"
                     " 2 arguments: channel <r|w>", (char *) NULL);
    return TCL_ERROR;
  }
  const char* channelName=argv[1];
  int writespec=1;
  if(argv[2][0]=='r') writespec=0;
  int result=UnlockChannel(interp,channelName,writespec);
  if(result<0) {
    Tcl_AppendResult(interp,"LOCKING ERROR: ",(char *) NULL);
    switch(result)
      {
      case -1:
        Tcl_AppendResult(interp,"Bad channel name",(char *) NULL);
        break;
      case -2:
        Tcl_AppendResult(interp,"Bad read/write mode",(char *) NULL);
        break;
      default:
        Tcl_AppendResult(interp,"Bad lock",(char *) NULL);
        break;
      }
    return TCL_ERROR;
  }
  if(result==0) Tcl_AppendResult(interp,"1",(char *) NULL); // Success
  else          Tcl_AppendResult(interp,"0",(char *) NULL); // Failure
  return TCL_OK;
}

static void MakeNice()
{
#if (SYSTEM_TYPE==WINDOWS)
  // Set solver to run at idle priority
  SetPriorityClass(OpenProcess(PROCESS_SET_INFORMATION,TRUE,
                               GetCurrentProcessId()),
                   NICE_DEFAULT);
#else
  nice(NICE_DEFAULT);
#endif
}

static int
Oc_MakeNice(ClientData, Tcl_Interp *interp, int argc, char **argv) 
{
  Tcl_ResetResult(interp);
  if (argc != 1) {
    Tcl_AppendResult(interp, argv[0], " takes no arguments", (char *) NULL);
    return TCL_ERROR;
  }
  MakeNice();
  return TCL_OK;
}

Tcl_Interp *
Oc_GlobalInterpreter()
{
  return globalInterp;
}

static void
ClearGlobalInterpreter(ClientData, Tcl_Interp *interp)
{
  if (interp == globalInterp) {
    globalInterp = (Tcl_Interp *) NULL;
  } else {
    Oc_AutoBuf ab;
    panic(ab("Global interpreter mismatch!"));
  }
}

void
Oc_SetDefaultTkFlag(int val)
{
  use_tk = val;
}

static const char* tk_usage_info="\n\
{-colormap window {Specify \"new\" to use private colormap}}\n\
{-display  display {Display to use}}\n\
{-geometry geometry {Initial geometry for window}}\n\
{-name     name {Name to use for application}}\n\
{-sync     {}     {Use synchronous mode for display server}}\n\
{-visual   visual {Visual for main window}}\n\
{-use      id {Id of window in which to embed application}}";

static int
separateArgumentStrings(Tcl_Interp *interp,
CONST84 char *wholestr,
int &tkc, CONST84 char * &tkstr,
int &appc, CONST84 char * &appstr,
int &consoleRequested)
{
  int i,j;
  struct _Option_Info {
    char* name;
    int count;  // >=0; Number of *additional* parameters
  };

  // Extract Tk options from tk_usage_info
  Oc_AutoBuf ab;
  int tkuic; CONST84 char ** tkuiv;

  // No argv?  Then no argument strings.
  Tcl_SplitList(interp,ab(tk_usage_info),&tkuic,&tkuiv);
  _Option_Info *oi=new _Option_Info[tkuic];
  for(j=0;j<tkuic;j++) {
    int optionc; CONST84 char ** optionv;
    // Split option list into 3 components: name, type(s), description
    Tcl_SplitList(interp,tkuiv[j],&optionc,&optionv);
    oi[j].name=new char[strlen(optionv[0])+1];
    strcpy(oi[j].name,optionv[0]);
    int typec; CONST84 char ** typev;
    // Parse type(s) list to determine parameter count
    Tcl_SplitList(interp,optionv[1],&typec,&typev);
    oi[j].count=typec;
    Tcl_Free((char*)typev); Tcl_Free((char*)optionv);
  }
  Tcl_Free((char*)tkuiv);

  // Note: tkstr and appstr are dynamically allocated.  It is
  //       the responsibility of the calling routine to call
  //       Tcl_Free to release their memory.
  int wc;  CONST84 char ** wv;  // Whole list
  if (wholestr == NULL) {
    wc = 0; wv = (CONST84 char **)Tcl_Alloc(1); (*wv) = NULL;
  } else if (Tcl_SplitList(interp,wholestr,&wc,&wv) != TCL_OK) {
    return TCL_ERROR;
  }

  int tc; CONST84 char ** tv=new CONST84 char *[wc];  // Tk's arg list
  int ac; CONST84 char ** av=new CONST84 char *[wc];  // App's arg list

  i=tc=ac=0;
  int opts_done=0;  // Terminate options processing, due to "--" flag.
  while(i<wc) {
    if(!opts_done) {
      if(strcmp("--",wv[i])==0) opts_done=1;
      else {
        for(j=0;j<tkuic;j++) {
          if(strcmp(oi[j].name,wv[i])==0) break; // Match found
        }
      }
    }
    if(!opts_done && j<tkuic) {
      // wv[i] is a recognized Tk argument.  Place it and
      // any additional parameters into tv.
      for(int k=0;k<1+oi[j].count;k++)
        tv[tc++]=wv[i++];
    } else {
      // wv[i] is not a recognized Tk argument.  Place it
      // into av;
      if (strcmp(wv[i],"-console") == 0) {
        consoleRequested = 1;
      }
      av[ac++]=wv[i++];
    }
  }

  // Create new list strings
  tkc=tc;  tkstr=Tcl_Merge(tc,tv);
  appc=ac; appstr=Tcl_Merge(ac,av);

  // Release unneeded arrays
  for(j=0;j<tkuic;j++) delete[] oi[j].name;
  delete[] oi;
  delete[] tv;
  delete[] av;
  Tcl_Free((char *)wv);

  return TCL_OK;
}

/* 
 * Removes argument "remove_index" from argument list by
 * translating all arguments after it down one spot and
 * decrementing argc.
 */
static void 
removeArg(int &argc, CONST84 char ** argv, int remove_index)
{ 
  if(remove_index<0 || remove_index>=argc) return;
  for(int j=remove_index+1;j<argc;j++) {
    argv[j-1]=argv[j];
  }
  argc--;
}

/*
 * Extract Oc options from Tcl argv variable.
 */
static void 
extractOcOptions(Tcl_Interp *interp)
{
  Oc_AutoBuf ab,ab1;
  char scratch[256];

  // Pull out and decompose Tcl argv variable
  int argc; CONST84 char ** argv; CONST84 char * argvstr;
  if((argvstr=Tcl_GetVar(interp,ab("argv"),TCL_GLOBAL_ONLY))==NULL) return;
  Tcl_SplitList(interp,argvstr,&argc,&argv);

  int i=0;
  while(i<argc) {
    if(strcmp(argv[i],"--")==0){
      // End flag processing
      break;
    }
    
    // Known options
    if(i+1<argc && strcmp(argv[i],"-tk")==0) {
      // Use/don't use Tk option: -tk BOOL
      use_tk=atoi(argv[i+1]);
      removeArg(argc,argv,i);
      removeArg(argc,argv,i);
    }
    else {
      // Anything else is left on the command line untouched, for
      // possible use by the shell script or other extensions.
      i++;
    }
  }

  // Re-assemble Tcl argv variable
  argvstr=Tcl_Merge(argc,argv);
  Tcl_SetVar(interp,ab("argv"),ab1(argvstr),TCL_GLOBAL_ONLY);
  sprintf(scratch,"%d",argc);
  Tcl_SetVar(interp,ab("argc"),scratch,TCL_GLOBAL_ONLY);

  // Release memory dynamically allocated by Tcl library routines.
  Tcl_Free((char *)argv);
  Tcl_Free((char *)argvstr);
}

int 
Oc_Init(Tcl_Interp *interp)
{
  Oc_AutoBuf ab,ab1,ab2,ab3,ab4;
  char scratch[256];

  if (globalInterp == (Tcl_Interp *)NULL) {
    globalInterp = interp;
    Tcl_CallWhenDeleted(interp, (Tcl_InterpDeleteProc *) ClearGlobalInterpreter,
            (ClientData) NULL);

  // Normally the function Oc_Init(Tcl_Interp *interp) will be called by 
  // the application initialization function of the application using this 
  // extension.  The conventional name for that function is 
  // Tcl_AppInit(Tcl_Interp *interp).  The argument 'interp' passed to 
  // Tcl_AppInit (and passed on to Oc_Init) was presumably returned
  // by a call to Tcl_CreateInterp(), usually from within Oc_Main().
  // Tcl_CreateInterp() contains a call to TclPlatformInit(), an internal
  // routine inside the Tcl library which takes care of basic 
  // platform-specific initialization.  This means that although Tcl_Init
  // hasn't yet been called (and therefore the interpreter is not fully
  // initialized), the interpreter is usable in a platform-independent
  // manner, and the Tcl variables tcl_pkgPath, tcl_library, tcl_platform,
  // tcl_patchLevel, tcl_version, tcl_precision and env(HOME) have already 
  // been set.  Also the "Tcl" package has already been provided.
  //
  // Until the Oc_Log cross-platform facility for displaying messages to 
  // the user is supported by evaluating the Tcl portion of this extension 
  // below, we must fall back on calls to panic().

  // Initialize Tcl.  
#ifdef CONFIG_TCL_LIBRARY
  // Set environment variable TCL_LIBRARY, unless already set.
  // We set this instead of tcl_library, because the tcl_library
  // is not inherited into slave interpreters.
  if(Tcl_GetVar2(interp,ab1("env"),ab2("TCL_LIBRARY"),
                 TCL_GLOBAL_ONLY)==NULL) {
    Tcl_SetVar2(interp,ab1("env"),ab2("TCL_LIBRARY"),
                ab3(OC_STRINGIFY(CONFIG_TCL_LIBRARY)),
                TCL_GLOBAL_ONLY);
  }
#endif
  if (Tcl_Init(interp) != TCL_OK) {
    if(strncmp("Can't find a usable",
               Tcl_GetStringResult(interp),19)==0) {
      panic(ab("Tcl initialization error:\n\n%s\n\n"
               "As a workaround, set the environment variable\n"
               "TCL_LIBRARY to the name of a directory\n"
               "containing an error-free init.tcl."),
            Tcl_GetStringResult(interp));
    } else {
      panic(ab("Tcl initialization error:\n\n%s"),
            Tcl_GetStringResult(interp));
    }
  }

  }

    if (Nullchannel_Init(interp) != TCL_OK) {
        panic(ab("Nullchannel initialization error:\n\n%s"),
                Tcl_GetStringResult(interp));
    }

  // Replace the exp() and pow() functions of Tcl's [expr] with versions
  // that will not raise exceptions on underflow.
  Tcl_ValueType argTypes[2] = {TCL_DOUBLE, TCL_DOUBLE};

  if (TCL_ERROR == Tcl_Eval(interp,ab("expr exp(-1000.)"))) {
    // exp() sets errno on underflow.  Wrap it
    Tcl_CreateMathFunc(interp,"exp",1,argTypes,Oc_Exp,NULL);
  }
  if (TCL_ERROR == Tcl_Eval(interp,ab("expr pow(10., -1000.)"))) {
    // pow() sets errno on underflow.  Wrap it
    Tcl_CreateMathFunc(interp,"pow",2,argTypes,Oc_Pow,NULL);
  }

  // Extract Oc options.  We must do this before initializing Tk,
  // because one of the Oc options is whether or not to use Tk.
  // However, we can not initialize Oc before Tk, because some parts
  // of Oc initialize differently if Tk is available.
  extractOcOptions(interp);

  // Initialize Tk, if requested.
  if (use_tk && (Tcl_PkgPresent(interp,ab("Tk"),NULL,0) == NULL)) {
    // Patch a bug in Tk 4.2
    if (Tcl_Eval(interp, ab(patchScript0)) != TCL_OK) {
      panic(ab("Tcl/Tk initialization error:\n\n"
               "Unable to patch installation bug in Tk 4.2\n\n"
               "Patch script returned error:%s"), Tcl_GetStringResult(interp));
    }

    // Break Tk arguments out of command line argument list.
    int tk_argc,app_argc;
    CONST84 char * orig_argv,*tk_argv,*app_argv;
    int consoleRequested = 0;
    orig_argv=Tcl_GetVar2(interp,ab("argv"),
                          (char *)NULL,TCL_GLOBAL_ONLY);
    separateArgumentStrings(interp,orig_argv,
                            tk_argc,tk_argv,
                            app_argc,app_argv,consoleRequested);

    // We have to call Tk_InitConsoleChannels before Tk_Init
    // (arguably a Tk bug).
    if (consoleRequested && (interp == globalInterp)) {
      Tk_InitConsoleChannels(interp);
    }

    // Initialize Tk, with argv copied from tk_argv
    Tcl_SetVar(interp,ab("argv"),ab1(tk_argv),TCL_GLOBAL_ONLY);
    sprintf(scratch,"%d",tk_argc);
    Tcl_SetVar(interp,ab("argc"),scratch,TCL_GLOBAL_ONLY);
    if (Tk_Init(interp) != TCL_OK) {
      if (globalInterp != interp) {
        return TCL_ERROR;
      }
      if(strncmp("Can't find a usable",
                 Tcl_GetStringResult(interp),19)==0) {
        panic(ab("Tk initialization error:\n\n%s\n\n"
                 "As a workaround, set the environment variable\n"
                 "TK_LIBRARY to the name of a directory\n"
                 "containing an error-free tk.tcl."),
              Tcl_GetStringResult(interp));
      } else {
        panic(ab("Tk initialization error:\n\n%s"),
              Tcl_GetStringResult(interp));
      }
    }

    // Reset argv with app arguments
    Tcl_SetVar(interp,ab("argv"),ab1(app_argv),TCL_GLOBAL_ONLY);
    sprintf(scratch,"%d",app_argc);
    Tcl_SetVar(interp,ab("argc"),scratch,TCL_GLOBAL_ONLY);
    Tcl_Free((char *)tk_argv); Tcl_Free((char *)app_argv);  // Release
 
    // Set up so that [info loaded] result includes Tk
    Tcl_StaticPackage(interp,ab("Tk"), Tk_Init, Tk_SafeInit);

    if (consoleRequested && (interp == globalInterp)) {
      if (Tk_CreateConsoleWindow(interp) != TCL_OK) {
        panic(ab("Tk console initialization error:\n\n%s"),
                Tcl_GetStringResult(interp));
      }
      Tcl_Eval(interp,ab("console hide"));
    }
  }

  // Initialize Oc
  // Register C++-based commands of this extension
#if (SYSTEM_TYPE==WINDOWS)
  Oc_RegisterCommand(interp,"Oc_WindowsMessageBox",
                     Oc_WindowsMessageBoxCmd);
#endif
  Oc_RegisterCommand(interp,"Oc_IgnoreSignal",
                     (Tcl_CmdProc *) Oc_IgnoreSignal);
  Oc_RegisterCommand(interp,"Oc_LockChannel",
                     (Tcl_CmdProc *) Oc_LockChannel);
  Oc_RegisterCommand(interp,"Oc_UnlockChannel",
                     (Tcl_CmdProc *) Oc_UnlockChannel);
  Oc_RegisterCommand(interp,"Oc_MakeNice",(Tcl_CmdProc *) Oc_MakeNice);
  Oc_RegisterCommand(interp,"Oc_SetPanicHeader",Oc_SetPanicHeaderCmd);

  if (Tcl_Eval(interp, ab(initScript0)) != TCL_OK
      || Tcl_Eval(interp, ab(initScript1)) != TCL_OK
      || Tcl_Eval(interp, ab(initScript2)) != TCL_OK
      || Tcl_Eval(interp, ab(initScript3)) != TCL_OK) {
    if (globalInterp != interp) {
      return TCL_ERROR;
    }
    panic(ab("Error in extension 'Oc':\n\n"
             "Error evaluating pre-initialization scripts:\n%s"),
          Tcl_GetStringResult(interp));
  }
  if (Oc_InitScript(interp, "Oc", OC_VERSION) != TCL_OK) {
    if (globalInterp != interp) {
      return TCL_ERROR;
    }
    panic(ab("%s"), Tcl_GetVar(interp, ab1("errorInfo"), TCL_GLOBAL_ONLY));
  }

  // At this stage Oc_Log is available for displaying error messages
  // to the user.

  /*
   * Constants from tcl.h, limits.h, and float.h.
   */
  if(Tcl_Eval(interp,
           ab("if {[catch {set config [Oc_Config RunPlatform]} msg]} "
              " {catch {Oc_Log Log $msg error} }")) != TCL_OK) {
    if (globalInterp != interp) {
      return TCL_ERROR;
    }
    panic(ab("Error in extension 'Oc':\n\n"
             "Error evaluating post-initialization scripts:\n%s"),
          Tcl_GetStringResult(interp));
  }

  char buf[1024];  // *Should* be big enough
  int errcount=0;
  Tcl_DString tbuf;
# define CTC_SET(name,format,macro)                                  \
  Oc_Snprintf(buf,sizeof(buf),                                       \
              "$config SetValue "  name  " "  format,macro);         \
  if(Tcl_Eval(interp,ab(buf)) != TCL_OK) {                           \
     if (globalInterp != interp) return TCL_ERROR;                   \
     Tcl_DStringInit(&tbuf);                                         \
     Tcl_DStringAppend(&tbuf, ab("Oc_Log Log"), -1);                 \
     Tcl_DStringAppendElement(&tbuf, Tcl_GetStringResult(interp));   \
     Tcl_DStringAppendElement(&tbuf, ab("error"));                   \
     Tcl_Eval(interp, Tcl_DStringValue(&tbuf));                      \
     Tcl_DStringFree(&tbuf);                                         \
     errcount++;                                                     \
  }
  CTC_SET("compiletime_tcl_patch_level","%s",TCL_PATCH_LEVEL)
  CTC_SET("compiletime_int_min","%d",INT_MIN)
  CTC_SET("compiletime_int_max","%d",INT_MAX)
  CTC_SET("compiletime_uint_max","%u",UINT_MAX)
  CTC_SET("compiletime_long_min","%ld",LONG_MIN)
  CTC_SET("compiletime_long_max","%ld",LONG_MAX)
  CTC_SET("compiletime_ulong_max","%lu",ULONG_MAX)
  CTC_SET("compiletime_flt_dig","%u",FLT_DIG)
  CTC_SET("compiletime_flt_epsilon","%.17g",FLT_EPSILON)
  CTC_SET("compiletime_flt_min","%.17g",FLT_MIN)
  CTC_SET("compiletime_flt_max","%.17g",FLT_MAX)
  CTC_SET("compiletime_dbl_dig","%u",DBL_DIG)
  CTC_SET("compiletime_dbl_epsilon","%.17g",DBL_EPSILON)
  CTC_SET("compiletime_dbl_min","%.17g",DBL_MIN)
  CTC_SET("compiletime_dbl_max","%.17g",DBL_MAX)
# undef CTC_SET
  if(Tcl_Eval(interp,ab("catch {unset config} ; catch {unset msg}"))
     != TCL_OK) {
     if (globalInterp != interp) return TCL_ERROR;
     Tcl_DStringInit(&tbuf);
     Tcl_DStringAppend(&tbuf, ab("Oc_Log Log"), -1);
     Tcl_DStringAppendElement(&tbuf, Tcl_GetStringResult(interp));
     Tcl_DStringAppendElement(&tbuf, ab("error"));
     Tcl_Eval(interp, Tcl_DStringValue(&tbuf));
     Tcl_DStringFree(&tbuf);
     errcount++;
  }

  // If we have no script to evaluate, parse the command line ourselves
  // to display a console
  if ((interp == globalInterp) && parseCommandLine) {
    if (Tcl_Eval(interp, ab("Oc_CommandLine Parse $argv")) != TCL_OK) {
        Tcl_DStringInit(&tbuf);
        Tcl_DStringAppend(&tbuf, ab("Oc_Log Log"), -1);
        Tcl_DStringAppendElement(&tbuf, Tcl_GetStringResult(interp));
        Tcl_DStringAppendElement(&tbuf, ab("error"));
        Tcl_Eval(interp, Tcl_DStringValue(&tbuf));
        Tcl_DStringFree(&tbuf);
        errcount++;
    }
  }
  if(errcount>0) return TCL_ERROR;

  return TCL_OK;
}

// Oc proxy for Tcl/Tk_Main
void Oc_Main(int argc,char** argv,Tcl_AppInitProc* appinit)
{
  char **my_argv = NULL;
  // Any program that uses Oc_Main is setting itself up to be a
  // shell program.  A shell program will treat its first argument
  // as the name of a file containing the script the shell should
  // evaluate, so long as that first argument does not begin with
  // the '-' character.
  //
  // If the shell program gets no arguments, or the first argument
  // does begin with the '-' character, then no script file argument
  // is seen by the shell program.  Instead, it will attempt to run
  // in an interactive mode, where the user can type commands to be
  // run interactively.  The following code sets up the interactive
  // mode by enabling Tk, and setting up to use Tk's interactive
  // console.  This is the only interactive mode we've been able to
  // make work properly across all the platforms and Tcl versions
  // we support.  Note that the additional options are appended at
  // the end, so that they force themselves to be effective.  For
  // example,
  //
  // $ shell -tk 0
  //
  // becomes effectively
  //
  // $ shell -tk 0 -tk 1 -console
  //
  // and the user request to disable Tk is overridden.  This is a bit
  // surprising, but preferable to starting up a mode that just doesn't
  // work at all.  If the user wants interactivity, they need Tk.
  //
  if ((argc == 1) || (argv[1][0] == '-')) {
    my_argv = new char*[argc+3];
    for (int i=0; i<argc; i++) my_argv[i] = argv[i];
    my_argv[argc] = new char[4];
    strcpy(my_argv[argc++],"-tk");
    my_argv[argc] = new char[2];
    strcpy(my_argv[argc++],"1");
    my_argv[argc] = new char[9];
    strcpy(my_argv[argc++],"-console");
    argv = my_argv;
    parseCommandLine = 1;
    // Note: The "new" calls above allocate a small amount of
    // memory which is never freed.  It is difficult to do
    // anything about this, however, because the Tk_Main call
    // below doesn't return.
  }
#ifndef NO_EXCEPTIONS
  try {
#endif
    Tk_Main(argc,argv,appinit);
#ifndef NO_EXCEPTIONS
  }
  catch (bad_alloc&) {
    panic(Oc_AutoBuf("Out of memory!\n"));
  }
  catch (EXCEPTION& e) {
    panic(Oc_AutoBuf("Uncaught standard exception: %s\n"
		     "Probably out of memory."),
          e.what());
  }
#endif
}

/*
 * Most commonly on Windows, but also in certain cirumstances on Unix,
 * a child process can get started without a standard channel.  Since
 * Tcl then assigns the next channel it opens to replace any missing
 * stdin, stdout, and stderr, in that order, this is a prescription for
 * unpredictable disaster.  To avoid that, we should check very early
 * for missing standard channels, and supply predictable null replacements
 * for them.
 *
 * It is also possible that Tcl (thinks it) has standard channels, but
 * I/O operations to those channels fail.  This can also bring down an
 * otherwise happy program, so we check for that and replace non-functional
 * standard channels with null replacements as well.
 *
 * Applications using the WinMain entry point should use the
 * VerifyWindowsTclStandardChannel() function to do a rigorous testing
 * of the standard channels.  Unix and Windows console apps can just
 * just use the Tcl_GetStdChannel() call to test.  Either way, in
 * failure use NullifyTclStandardChannel to replace the bad channel
 * with a null channel.
 *
 * NOTE: These routines have no relation to the platform-specific standard
 * channels available at the C/C++ level: fprintf(std*, ...), cout << ..., etc.
 *
 */
static void
NullifyTclStandardChannel(int type) {
    Tcl_Channel channel;
    // Create a null channel to replace a presumably broken standard channel
    if ((channel = Nullchannel_Open()) == NULL) {
        switch (type) {
            case TCL_STDIN:
                panic(Oc_AutoBuf("Couldn't create a standard input channel"));
            case TCL_STDOUT:
                panic(Oc_AutoBuf("Couldn't create a standard output channel"));
            case TCL_STDERR:
                panic(Oc_AutoBuf("Couldn't create a standard error channel"));
            default:
                panic(Oc_AutoBuf("Bad standard channel type: %d"), type);
        }
    }
    /* This may not be necessary, since Tcl_CreateChannel automatically
     * sets empty standard channels, but it shouldn't hurt.
     */
    Tcl_SetStdChannel(channel, type);
}

/*
 * The common startup initializations for all platforms
 */
void
CommonMain(CONST char* exename) {
    // Initialize the panic() function to use our customized
    // message display routines.
    Oc_InitPanic(exename);

    // Tcl_FindExecutable does some double-secret special
    // initializations without which some Tcl C APIs (like
    // Tcl_CreateChannel()) will crash.  So, call it here.
    Tcl_FindExecutable(Oc_AutoBuf(exename));
}

#if (SYSTEM_TYPE==WINDOWS)
static int
VerifyWindowsTclStandardChannel(int type) {
  /*
   * On Windows, programs with entry point WinMain() which are launched
   * from the operating system do not have any standard channels.
   * When such programs are launched as subprocesses by another
   * program via the CreateProcess() system call, the parent
   * process can pass standard channels to the subprocess.  The
   * startInfo structure retrieved from the OS contains
   * information about what instructions the parent process 
   * supplied for this program.  If the STARTF_USESTDHANDLES bit
   * of the flag array startInfo.dwFlags is set, then the parent
   * process provided standard channels to this process.  In that
   * case, it is safe to call the Tcl library function
   * Tcl_GetStdChannel().  
   *
   * This routine returns 1 if channel looks okay, 0 otherwise.
   */
  STARTUPINFO startInfo;
  GetStartupInfo(&startInfo);
  if (startInfo.dwFlags & STARTF_USESTDHANDLES) {
    Tcl_Channel channel;
    if ((channel=Tcl_GetStdChannel(type)) != NULL) {
      /*
       * On at least some Windows platforms for at least some version(s)
       * of Tcl/Tk, we've found that Tcl_GetStdChannel() returns non-NULL
       * even when it is not returning a valid channel.  Subsequent I/O
       * on the returned channel returns the error "Bad file number".
       * Testing the returned pointer with Tcl_Tell can be used to check
       * for this -- it returns 0 on good channels, -1 on bad.
       *
       * We must be sure *not* to use the Tcl_Tell test on Unix, since
       * the standard channels are often not seekable, even though they
       * are valid on Unix.  Although replacement with a null channel
       * ought to harmless, experience proves it is not.
       *
       * Wish I had time to really figure all this out...
       */
      if (Tcl_Tell(channel)>=0) {
	return 1;     // Std channel verified!
      } else {
	/*
	 * Tcl_GetStdChannel thought it had a channel, but
	 * it doesn't work.
	 */
	Tcl_UnregisterChannel(NULL,channel);
      }
    }
  }   // close if (startInfo....
  return 0; // Bad channel
}

/*
 * The main entry point for OOMMF applications on Windows platforms.
 */
int APIENTRY 
WinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */,
        LPSTR lpszCmdLine, int /* nCmdShow */)
{
    Oc_AutoBuf ab;

    // Windows doesn't pass the application name as the first word on the
    // command line like Unix does.  Instead a system call provides the
    // application name (as a full pathname).
    // NOTE: This fixed-length buffer should be long enough even for a full
    // pathname.  Dynamic allocation from the heap is avoided since
    // Tcl_Main and Tk_Main never return, and we'll have no chance to 
    // free the memory.  It's not clear whether Windows will free memory
    // when the process exits.
    char *p,exename[1024];
    GetModuleFileName(NULL, exename, sizeof(exename));
    // Translate '\' to '/'
    for (p = exename; *p != '\0'; p++) {
        if (*p == '\\') *p = '/';
    }
    for (p = lpszCmdLine; *p != '\0'; p++) {
        if (*p == '\\') *p = '/';
    }

    CommonMain(exename);

    // Setup standard channels
    if(!VerifyWindowsTclStandardChannel(TCL_STDIN)) {
      NullifyTclStandardChannel(TCL_STDIN);
    }
    if(!VerifyWindowsTclStandardChannel(TCL_STDOUT)) {
      NullifyTclStandardChannel(TCL_STDOUT);
    }
    if(!VerifyWindowsTclStandardChannel(TCL_STDERR)) {
      NullifyTclStandardChannel(TCL_STDERR);
    }

  // Windows doesn't tokenize the command line
  int argc;
  char *argv[256];
  CONST84 char **argv_temp;
  char cmdline[4096];
  if(Tcl_SplitList(NULL,lpszCmdLine,&argc,&argv_temp)!=TCL_OK) {
    panic(ab("Bad command line: %s"),lpszCmdLine);
  }
  if(argc+1>sizeof(argv)/sizeof(argv[0])) {
    panic(ab("Too many command line parameters: %d>%d"),
              argc+1,sizeof(argv)/sizeof(argv[0]));
  }

  // Copy split string out of temporary memory, and setup
  // argv pointers.
  argv[0]=exename;
  int i,total_length=0;
  for(i=0;i<argc;i++) {
    argv[i+1]=cmdline+total_length;
    total_length+=strlen(argv_temp[i])+1;
    if(total_length>sizeof(cmdline)) {
      panic(ab("Parsed command line too long (%d)"),sizeof(cmdline));
    }
    strcpy(argv[i+1],argv_temp[i]);
  }
  argc++;
  Tcl_Free((char *)argv_temp);

  // Now we can pretend a Unix system gave us argc and argv
  return Oc_AppMain(argc, argv);
}

#endif // SYSTEM_TYPE == WINDOWS

/*
 * The main entry point for OOMMF applications on Unix platforms.
 * ... and Windows console apps.
 */
int 
main(int argc, char **argv)
{
    CommonMain(argv[0]);

    // Setup standard channels.  Use a less stingent testing
    // regime than in the WinMain setting.
    if(Tcl_GetStdChannel(TCL_STDIN)==NULL) {
      NullifyTclStandardChannel(TCL_STDIN);
    }
    if(Tcl_GetStdChannel(TCL_STDOUT)==NULL) {
      NullifyTclStandardChannel(TCL_STDOUT);
    }
    if(Tcl_GetStdChannel(TCL_STDERR)==NULL) {
      NullifyTclStandardChannel(TCL_STDERR);
    }

    return Oc_AppMain(argc, argv);
}

/*
 * This snippet sets up the entry point for Cygwin/GUI apps.
 * An alternative to this is to add '-e _mainCRTStartup' to
 * the gcc/g++ link line.
 */
#ifdef __CYGWIN__
extern "C" {
int mainCRTStartup();
int WinMainCRTStartup();
}
int WinMainCRTStartup() { mainCRTStartup(); return 0; }
#endif
