/* 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 #include #include /* Header file for this extension */ #include "oc.h" /* Implementation-defined limits (ANSI C) */ #include #include 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 ", (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 ", (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=argc) return; for(int j=remove_index+1;j0) 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=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;isizeof(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