/***
*crtexe.c - Initialization for console EXE using CRT DLL
*
*       Copyright (c) 1991-2001, Microsoft Corporation. All rights reserved.
*
*Purpose:
*       This is the actual startup routine for apps linking to the CRT DLL.
*       It calls the user's main routine [w]main() or [w]WinMain after
*       performing C Run-Time Library initialization.
*
*       With ifdefs, this source file also provides the source code for:
*       wcrtexe.c   the startup routine for console apps with wide chars
*       crtexew.c   the startup routine for Windows apps
*       wcrtexew.c  the startup routine for Windows apps with wide chars
*
*Revision History:
*       08-12-91  GJF   Module created.
*       01-05-92  GJF   Substantially revised
*       01-17-92  GJF   Restored Stevewo's scheme for unhandled exceptions.
*       01-29-92  GJF   Added support for linked-in options (equivalents of
*                       binmode.obj, commode.obj and setargv.obj).
*       04-17-92  SKS   Add call to _initterm() to do C++ constructors (I386)
*       08-01-92  SRW   winxcpt.h replaced bu excpt.h which is included by
*                       oscalls.h
*       09-16-92  SKS   Prepare for C8 C++ for MIPS by calling C++ constructors
*       04-02-93  SKS   Change try/except to __try/__except
*       04-06-93  SKS   Replace _CRTAPI* with _cdecl
*                       Change __GetMainArgs to __getmainargs
*                       Change fmode/commode implementation to reflect the
*                       the change to _declspec(dllimport) for imported data.
*       04-12-93  CFW   Added xia..xiz initializers and initializer call.
*       04-26-93  GJF   Made lpszCommandLine (unsigned char *) to deal with
*                       chars > 127 in the command line.
*       04-27-93  GJF   Removed support for _RT_STACK, _RT_INTDIV,
*                       _RT_INVALDISP and _RT_NONCONT.
*       05-14-93  GJF   Added support for quoted program names.
*       05-24-93  SKS   Add support for special versions of _onexit/atexit
*                       which do one thing for EXE's and another for DLLs.
*       10-19-93  CFW   MIPS support for _imp__xxx.
*       10-21-93  GJF   Added support for NT's crtdll.dll.
*       11-08-93  GJF   Guard the initialization code with the __try -
*                       __except statement (esp., C++ constructors for static
*                       objects).
*       11-09-93  GJF   Replaced PF with _PVFV (defined in internal.h).
*       11-23-93  CFW   Wide char enable.
*       12-07-93  CFW   Change _TCHAR to _TSCHAR.
*       01-04-94  CFW   Pass copy of environment to main.
*       01-11-94  GJF   Call __GetMainArgs instead of __getmainargs for NT
*                       SDK build (same function, different name)
*       01-28-94  CFW   Move environment copying to setenv.c.
*       03-04-94  SKS   Remove __setargv from this module to avoid link-time
*                       problems compiling -MD and linking with setargv.obj.
*                       static _dowildcard becomes an extern.  Add another
*                       parameter to _*getmainargs (_newmode).
*                       NOTE: These channges do NOT apply to the _NTSDK.
*       03-28-94  SKS   Add call to _setdefaultprecision for X86 (not _NTSDK)
*       04-01-94  GJF   Moved declaration of __[w]initenv to internal.h.
*       04-06-94  GJF   _IMP___FMODE, _IMP___COMMODE now evaluate to
*                       dereferences of function returns for _M_IX86 (for
*                       compatability with Win32s version of msvcrt*.dll).
*       04-25-94  CFW   wWinMain has Unicode args.
*       05-16-94  SKS   Get StartupInfo to give correct nCmdShow parameter
*                       to (_w)WinMain() (was ALWAYS passing SW_SHOWDEFAULT).
*       08-04-94  GJF   Added support for user-supplied _matherr routine
*       09-06-94  GJF   Added code to set __app_type properly.
*       10-04-94  BWT   Fix _NTSDK build
*       02-22-95  JWM   Spliced in PMAC code.
*       05-24-95  CFW   Official ANSI C++ new handler added.
*       07-24-95  CFW   Change PMac onexit malloc to _malloc_crt.
*       06-27-96  GJF   Replaced defined(_WIN32) wint !defined(_MAC). Cleaned
*                       up the format a bit.
*       08-01-96  RDK   For PMac, set onexit table pointers to -1 to parallel
*                       x86 functionality.
*       04-28-99  PML   Wrap __declspec(allocate()) in _CRTALLOC macro.
*       05-11-99  KBF   Wrap RTC support in #ifdef.
*       05-17-99  PML   Remove all Macintosh support.
*       11-13-99  PML   Create 4 new COM+ specific entrypoints which return
*                       instead of calling exit().
*       11-16-99  PML   ... and remove them - linker problems mean new
*                       entrypoints don't work.  Instead, look directly at the
*                       COM Descriptor Image Directory entry in the optional
*                       header to see if we are a COM+ app.
*       02-16-00  GB    Changed GetModuleHandle to GetModuleHandleA in
*                       check_complus_app.
*       08-04-00  PML   check_complus_app -> check_managed_app (VS7#117746).
*       08-22-00  GB    Changed GetModuleHandle to GetModuleHandleA in
*                       mainCRTStartup().
*       12-09-00  PML   Tighten up check_managed_app tests (VS7#167293).
*       03-27-01  PML   report _RT_SPACEARG on __[w]getmainargs error
*                       (VS7#231220).
*       04-30-01  BWT   Don't do this for SYSCRT. Need to run against old msvcrt.dll's
*                       that don't return error.
*                       Remove _NTSDK.
*
*******************************************************************************/

#ifdef  CRTDLL

/*
 * SPECIAL BUILD MACROS! Note that crtexe.c (and crtexew.c) is linked in with
 * the client's code. It does not go into crtdll.dll! Therefore, it must be
 * built under the _DLL switch (like user code) and CRTDLL must be undefined.
 * The symbol SPECIAL_CRTEXE is turned on to suppress the normal CRT DLL
 * definition of _fmode and _commode using __declspec(dllexport).  Otherwise
 * this module would not be able to refer to both the local and DLL versions
 * of these two variables.
 */

#undef  CRTDLL
#undef _DLL
#define _DLL

#define SPECIAL_CRTEXE

#include <cruntime.h>
#include <oscalls.h>
#include <internal.h>
#include <process.h>
#include <math.h>
#include <rterr.h>
#include <stdlib.h>
#include <tchar.h>
#include <rtcapi.h>
#include <sect_attribs.h>


/*
 * wWinMain is not yet defined in winbase.h. When it is, this should be
 * removed.
 */

int
WINAPI
wWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPWSTR lpCmdLine,
    int nShowCmd
    );

#define SPACECHAR   _T(' ')
#define DQUOTECHAR  _T('\"')

#ifdef  _M_IX86

/*
 * The local copy of the Pentium FDIV adjustment flag
 * and the address of the flag in MSVCRT*.DLL.
 */

extern int _adjust_fdiv;

extern int * _imp___adjust_fdiv;

#endif


/* default floating point precision - X86 only! */

#ifdef  _M_IX86
extern void _setdefaultprecision();
#endif


/*
 * Declare function used to install a user-supplied _matherr routine.
 */
_CRTIMP void __setusermatherr( int (__cdecl *)(struct _exception *) );


/*
 * Declare the names of the exports corresponding to _fmode and _commode
 */
#ifdef _M_IX86

#define _IMP___FMODE    (__p__fmode())
#define _IMP___COMMODE  (__p__commode())

#else   /* ndef _M_IX86 */

/* assumed to be MIPS or Alpha */

#define _IMP___FMODE    __imp__fmode
#define _IMP___COMMODE  __imp__commode

#endif  /* _M_IX86 */

#if     !defined(_M_IX86)
extern int * _IMP___FMODE;      /* exported from the CRT DLL */
extern int * _IMP___COMMODE;    /* these names are implementation-specific */
#endif

extern int _fmode;          /* must match the definition in <stdlib.h> */
extern int _commode;        /* must match the definition in <internal.h> */
extern int _dowildcard;     /* passed to __getmainargs() */

/*
 * Declare/define communal that serves as indicator the default matherr
 * routine is being used.
 */
int __defaultmatherr;

/*
 * routine in DLL to do initialization (in this case, C++ constructors)
 */
extern void __cdecl _initterm(_PVFV *, _PVFV *);

/*
 * routine to check if this is a managed application
 */
static int __cdecl check_managed_app(void);

/*
 * pointers to initialization sections
 */
extern _CRTALLOC(".CRT$XIA") _PVFV __xi_a[];
extern _CRTALLOC(".CRT$XIZ") _PVFV __xi_z[];    /* C initializers */
extern _CRTALLOC(".CRT$XCA") _PVFV __xc_a[];
extern _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[];    /* C++ initializers */

/*
 * Pointers to beginning and end of the table of function pointers manipulated
 * by _onexit()/atexit().  The atexit/_onexit code is shared for both EXE's and
 * DLL's but different behavior is required.  These values are set to -1 to
 * mark this module as an EXE.
 */

extern _PVFV *__onexitbegin;
extern _PVFV *__onexitend;


/***
*mainCRTStartup(void)
*wmainCRTStartup(void)
*WinMainCRTStartup(void)
*wWinMainCRTStartup(void)
*
*Purpose:
*       These routines do the C runtime initialization, call the appropriate
*       user entry function, and handle termination cleanup.  For a managed
*       app, they then return the exit code back to the calling routine, which
*       is the managed startup code.  For an unmanaged app, they call exit and
*       never return.
*
*       Function:               User entry called:
*       mainCRTStartup          main
*       wmainCRTStartup         wmain
*       WinMainCRTStartup       WinMain
*       wWinMainCRTStartup      wWinMain
*
*Entry:
*
*Exit:
*       Managed app: return value from main() et al, or the exception code if
*                 execution was terminated by the __except guarding the call
*                 to main().
*       Unmanaged app: never return.
*
*******************************************************************************/

#ifdef  _WINMAIN_

#ifdef  WPRFLAG
int wWinMainCRTStartup(
#else
int WinMainCRTStartup(
#endif

#else   /* ndef _WINMAIN_ */

#ifdef  WPRFLAG
int wmainCRTStartup(
#else
int mainCRTStartup(
#endif

#endif  /* _WINMAIN_ */
        void
        )
{
        int argc;   /* three standard arguments to main */
        _TSCHAR **argv;
        _TSCHAR **envp;

        int argret;
        int mainret;
        int managedapp;

#ifdef  _WINMAIN_
        _TUCHAR *lpszCommandLine;
        STARTUPINFO StartupInfo;
#endif
        _startupinfo    startinfo;        

        /*
         * Determine if this is a managed application
         */
        managedapp = check_managed_app();

        /*
         * Guard the initialization code and the call to user's main, or
         * WinMain, function in a __try/__except statement.
         */

        __try {
            /*
             * Set __app_type properly
             */
#ifdef  _WINMAIN_
            __set_app_type(_GUI_APP);
#else
            __set_app_type(_CONSOLE_APP);
#endif

            /*
             * Mark this module as an EXE file so that atexit/_onexit
             * will do the right thing when called, including for C++
             * d-tors.
             */
            __onexitbegin = __onexitend = (_PVFV *)(-1);

            /*
             * Propogate the _fmode and _commode variables to the DLL
             */
            *_IMP___FMODE = _fmode;
            *_IMP___COMMODE = _commode;

#ifdef  _M_IX86
            /*
             * Set the local copy of the Pentium FDIV adjustment flag
             */

            _adjust_fdiv = * _imp___adjust_fdiv;
#endif

            /*
             * Run the RTC initialization code for this DLL
             */
#ifdef  _RTC
            _RTC_Initialize();
#endif

            /*
             * Call _setargv(), which will trigger a call to __setargv() if
             * SETARGV.OBJ is linked with the EXE.  If SETARGV.OBJ is not
             * linked with the EXE, a dummy _setargv() will be called.
             */
#ifdef  WPRFLAG
            _wsetargv();
#else
            _setargv();
#endif

            /*
             * If the user has supplied a _matherr routine then set
             * __pusermatherr to point to it.
             */
            if ( !__defaultmatherr )
                __setusermatherr(_matherr);

#ifdef  _M_IX86
            _setdefaultprecision();
#endif

            /*
             * Do runtime startup initializers.
             *
             * Note: the only possible entry we'll be executing here is for
             * __lconv_init, pulled in from charmax.obj only if the EXE was
             * compiled with -J.  All other .CRT$XI* initializers are only
             * run as part of the CRT itself, and so for the CRT DLL model
             * are not found in the EXE.  For that reason, we call _initterm,
             * not _initterm_e, because __lconv_init will never return failure,
             * and _initterm_e is not exported from the CRT DLL.
             *
             * Note further that, when using the CRT DLL, executing the
             * .CRT$XI* initializers is only done for an EXE, not for a DLL
             * using the CRT DLL.  That is to make sure the -J setting for
             * the EXE is not overriden by that of any DLL.
             */
            _initterm( __xi_a, __xi_z );

#ifdef  _RTC
            atexit(_RTC_Terminate);
#endif

            /*
             * Get the arguments for the call to main. Note this must be
             * done explicitly, rather than as part of the dll's
             * initialization, to implement optional expansion of wild
             * card chars in filename args
             */

            startinfo.newmode = _newmode;

#ifdef  ANSI_NEW_HANDLER
            startinfo.newh = _defnewh;
#endif  /* ANSI_NEW_HANDLER */

#ifdef  WPRFLAG
            argret = __wgetmainargs(&argc, &argv, &envp,
                                    _dowildcard, &startinfo);
#else
            argret = __getmainargs(&argc, &argv, &envp,
                                   _dowildcard, &startinfo);
#endif

#ifndef _SYSCRT
            if (argret < 0)
                _amsg_exit(_RT_SPACEARG);
#endif

            /*
             * do C++ constructors (initializers) specific to this EXE
             */
            _initterm( __xc_a, __xc_z );

#ifdef  _WINMAIN_
            /*
             * Skip past program name (first token in command line).
             * Check for and handle quoted program name.
             */
#ifdef  WPRFLAG
            /* OS may not support "W" flavors */
            if (_wcmdln == NULL)
                return 255;
            lpszCommandLine = (wchar_t *)_wcmdln;
#else
            lpszCommandLine = (unsigned char *)_acmdln;
#endif

            if ( *lpszCommandLine == DQUOTECHAR ) {
                /*
                 * Scan, and skip over, subsequent characters until
                 * another double-quote or a null is encountered.
                 */
                while ( *++lpszCommandLine && (*lpszCommandLine
                        != DQUOTECHAR) );
                /*
                 * If we stopped on a double-quote (usual case), skip
                 * over it.
                 */
                if ( *lpszCommandLine == DQUOTECHAR )
                    lpszCommandLine++;
            }
            else {
                while (*lpszCommandLine > SPACECHAR)
                    lpszCommandLine++;
            }

            /*
             * Skip past any white space preceeding the second token.
             */
            while (*lpszCommandLine && (*lpszCommandLine <= SPACECHAR)) {
                lpszCommandLine++;
            }

            StartupInfo.dwFlags = 0;
            GetStartupInfo( &StartupInfo );

#ifdef  WPRFLAG
            mainret = wWinMain(
#else
            mainret = WinMain(
#endif
                       GetModuleHandleA(NULL),
                       NULL,
                       lpszCommandLine,
                       StartupInfo.dwFlags & STARTF_USESHOWWINDOW
                        ? StartupInfo.wShowWindow
                        : SW_SHOWDEFAULT
                      );
#else   /* _WINMAIN_ */

#ifdef  WPRFLAG
            __winitenv = envp;
            mainret = wmain(argc, argv, envp);
#else
            __initenv = envp;
            mainret = main(argc, argv, envp);
#endif

#endif  /* _WINMAIN_ */

            if ( !managedapp )
                exit(mainret);

            _cexit();

        }
        __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
        {
            /*
             * Should never reach here
             */

            mainret = GetExceptionCode();

            if ( !managedapp )
                _exit(mainret);

            _c_exit();

        } /* end of try - except */

        return mainret;
}

/***
*check_managed_app() - Check for a managed executable
*
*Purpose:
*       Determine if the EXE the startup code is linked into is a managed app
*       by looking for the COM Runtime Descriptor in the Image Data Directory
*       of the PE or PE+ header.
*
*Entry:
*       None
*
*Exit:
*       1 if managed app, 0 if not.
*
*Exceptions:
*
*******************************************************************************/

static int __cdecl check_managed_app (
        void
        )
{
        PIMAGE_DOS_HEADER pDOSHeader;
        PIMAGE_NT_HEADERS pPEHeader;
        PIMAGE_OPTIONAL_HEADER32 pNTHeader32;
        PIMAGE_OPTIONAL_HEADER64 pNTHeader64;

        pDOSHeader = (PIMAGE_DOS_HEADER)GetModuleHandleA(NULL);
        if ( pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE )
            return 0;

        pPEHeader = (PIMAGE_NT_HEADERS)((char *)pDOSHeader +
                                        pDOSHeader->e_lfanew);
        if ( pPEHeader->Signature != IMAGE_NT_SIGNATURE )
            return 0;

        pNTHeader32 = (PIMAGE_OPTIONAL_HEADER32)&pPEHeader->OptionalHeader;
        switch ( pNTHeader32->Magic ) {
        case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
            /* PE header */
            if ( pNTHeader32->NumberOfRvaAndSizes <=
                    IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )
                return 0;
            return !! pNTHeader32 ->
                      DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] .
                      VirtualAddress;
        case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
            /* PE+ header */
            pNTHeader64 = (PIMAGE_OPTIONAL_HEADER64)pNTHeader32;
            if ( pNTHeader64->NumberOfRvaAndSizes <=
                    IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )
                return 0;
            return !! pNTHeader64 ->
                      DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] .
                      VirtualAddress;
        }

        /* Not PE or PE+, so not managed */
        return 0;
}

#endif  /* CRTDLL */