从零开始,学习windows编程(4)--从libc.lib开始

从上一篇文章中,大家已经了解到有C运行时库这个概念,这个不算是新东西,但是一般都隐藏在幕后,C/C++语言教学的时候不讲,windows/linux编程的时候似乎也不会专门讲到。不过它一般是我们C/C++编程中默认会使用的一个重要部分。回想想,我们随手打出的strcpy, memset, memcpy等等,不就是C运行时库所提供出来的东西吗?

既然这样,就要好好研究一下这个东西了。

前面已经说过,针对单线程/多线程,静态/动态链接,是否是debug版本,VC6的C运行时库提供了6个版本。具体可以看下面的截图。

image

而其中每一个选择对应的LIB文件,在上一篇中已经有一个列表介绍了,这里就不重复写了。这里也不全部一下子将所有的都研究一下,还是按照由浅入深的原则,从最简单的部分开始,当然,也会在牵涉到其他部分的时候,进行一定的说明。

最简单的当然是Single-Threaded,同时也是static link的了。其对应的文件为LIBC.LIB。对应CL的编译选项为/ML。

既然要研究这个LIB文件,那当然是有源码最好了,jjhou不是说过,“源码面前,了无秘密”吗。那我们在哪里找到有源码呢?

只要你安装了VC6,它就带有CRT的源码,具体目录和你安装VC6的目录有关,在我电脑上的路径为“d:\Program Files\Microsoft Visual Studio\VC98\CRT\SRC\”,你进去之后会发现,里面有不少熟悉的名字,如“MATH.H”、“STDIO.H”、“STDLIB.H”、“STRING.H”等,由于C运行时库被C++运行时库包含,所以这里面还有C++标准库的代码,所以还能看到“IOSTREAM”、“CSTDIO”、“algorithm”等C++的std头文件。

这里就出现了一个问题,这里的文件有成百上千个,我们一个个全部看是不可能的,那如何找出关键的部分来呢?

如果还对上一篇的分析有印象,并且带有问题的同学,应该很容易联想到,我们在link不带有defaultlibs信息的hello.obj文件时,出现了两个LINK2001错误,分别是找不到_printf和_mainCRTStartup这两个symbol文件。

编译器CL在编译C程序的时候,内部将需要编译的函数前面加上下划线(_)用来标识,_printf具体指的就是printf函数,_mainCRTStartup则是mainCRTStartup,是在哪里使用的呢?

我们已经知道,printf函数和mainCRTStartup函数的实现都是在LIBC.LIB中,printf,是我们main函数中用来打印信息的,而mainCRTStartup,则是hello.exe的入口(entry point)。

入口

学过C语言的人都知道,有一个main函数,是一个程序的入口。不管怎样,main函数是特殊的。在TCPL (“The C programming Language” by K&R) 的1.1章节,介绍Hello World的时候说的一段话:

Now, for some explanations about the program itself. A C program, whatever its size, consists of functions and variables. A function contains statements that specify the computing operations to be done, and variables store values used during the computation. C functions are like the subroutines and functions in Fortran or the procedures and functions of Pascal. Our example is a function named main. Normally you are at liberty to give functions whatever names you like, but “main” is special - your program begins executing at the beginning of main. This means that every program must have a main somewhere.

因为这一段话,因为这本书的经典,很多人包括我思路都很难转变。一直都认为main就是C程序的入口函数。不过,真的是这样吗?

使用汇编的童鞋都知道,汇编的入口函数只是一个符号,是可以随意定义的,之后就从入口开始,PC一条条的开始执行汇编代码。对于C程序来说,main也是一个符号而已,不过这个符号与汇编的_start有些区别,_start可以用其他符号直接代替,而在windows系统下,VC开发的环境中,我们的hello.exe的main函数之前还有一个mainCRTStartup(呼,好多限制条件,好绕口……)。

crt0.c

为什么需要mainCRTStartup呢,我们还是要看一下源码实现。先搜索到mainCRTStartup所在的文件,为crt0.c,其全部代码如下:

/***
*crt0.c - C runtime initialization routine
*
*       Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
*
*Purpose:
*       This the actual startup routine for apps.  It calls the user's main
*       routine [w]main() or [w]WinMain after performing C Run-Time Library
*       initialization.
*
*       (With ifdef's, this source file also provides the source code for
*       wcrt0.c, the startup routine for console apps with wide characters,
*       wincrt0.c, the startup routine for Windows apps, and wwincrt0.c,
*       the startup routine for Windows apps with wide characters.)
*
*******************************************************************************/

#ifdef _WIN32

#ifndef CRTDLL

#include <cruntime.h>
#include <dos.h>
#include <internal.h>
#include <stdlib.h>
#include <string.h>
#include <rterr.h>
#include <windows.h>
#include <awint.h>
#include <tchar.h>
#include <dbgint.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
    );

#ifdef WPRFLAG
_TUCHAR * __cdecl _wwincmdln(void);
#else  /* WPRFLAG */
_TUCHAR * __cdecl _wincmdln(void);
#endif  /* WPRFLAG */

/*
 * command line, environment, and a few other globals
 */

#ifdef WPRFLAG
wchar_t *_wcmdln;           /* points to wide command line */
#else  /* WPRFLAG */
char *_acmdln;              /* points to command line */
#endif  /* WPRFLAG */

char *_aenvptr = NULL;      /* points to environment block */
wchar_t *_wenvptr = NULL;   /* points to wide environment block */


void (__cdecl * _aexit_rtn)(int) = _exit;   /* RT message return procedure */

static void __cdecl fast_error_exit(int);   /* Error exit via ExitProcess */

/*
 * _error_mode and _apptype, together, determine how error messages are
 * written out.
 */
int __error_mode = _OUT_TO_DEFAULT;
#ifdef _WINMAIN_
int __app_type = _GUI_APP;
#else  /* _WINMAIN_ */
int __app_type = _CONSOLE_APP;
#endif  /* _WINMAIN_ */


/***
*BaseProcessStartup(PVOID Peb)
*
*Purpose:
*       This routine does the C runtime initialization, calls main(), and
*       then exits.  It never returns.
*
*Entry:
*       PVOID Peb - pointer to Win32 Process Environment Block (not used)
*
*Exit:
*       This function never returns.
*
*******************************************************************************/

#ifdef _WINMAIN_

#ifdef WPRFLAG
void wWinMainCRTStartup(
#else  /* WPRFLAG */
void WinMainCRTStartup(
#endif  /* WPRFLAG */

#else  /* _WINMAIN_ */

#ifdef WPRFLAG
void wmainCRTStartup(
#else  /* WPRFLAG */
void mainCRTStartup(
#endif  /* WPRFLAG */

#endif  /* _WINMAIN_ */
        void
        )

{
        int mainret;

#ifdef _WINMAIN_
        _TUCHAR *lpszCommandLine;
        STARTUPINFO StartupInfo;
#endif  /* _WINMAIN_ */

        /*
         * Get the full Win32 version
         */
        _osver = GetVersion();

        _winminor = (_osver >> 8) & 0x00FF ;
        _winmajor = _osver & 0x00FF ;
        _winver = (_winmajor << 8) + _winminor;
        _osver = (_osver >> 16) & 0x00FFFF ;

#ifdef _MT
        if ( !_heap_init(1) )               /* initialize heap */
#else  /* _MT */
        if ( !_heap_init(0) )               /* initialize heap */
#endif  /* _MT */
            fast_error_exit(_RT_HEAPINIT);  /* write message and die */

#ifdef _MT
        if( !_mtinit() )                    /* initialize multi-thread */
            fast_error_exit(_RT_THREAD);    /* write message and die */
#endif  /* _MT */

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

        __try {

            _ioinit();                      /* initialize lowio */

#ifdef WPRFLAG
            /* get wide cmd line info */
            _wcmdln = (wchar_t *)__crtGetCommandLineW();

            /* get wide environ info */
            _wenvptr = (wchar_t *)__crtGetEnvironmentStringsW();

            _wsetargv();
            _wsetenvp();
#else  /* WPRFLAG */
            /* get cmd line info */
            _acmdln = (char *)GetCommandLineA();

            /* get environ info */
            _aenvptr = (char *)__crtGetEnvironmentStringsA();

            _setargv();
            _setenvp();
#endif  /* WPRFLAG */

            _cinit();                       /* do C data initialize */

#ifdef _WINMAIN_

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

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

#ifdef WPRFLAG
            __winitenv = _wenviron;
            mainret = wmain(__argc, __wargv, _wenviron);
#else  /* WPRFLAG */
            __initenv = _environ;
            mainret = main(__argc, __argv, _environ);
#endif  /* WPRFLAG */

#endif  /* _WINMAIN_ */
            exit(mainret);
        }
        __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
        {
            /*
             * Should never reach here
             */
            _exit( GetExceptionCode() );

        } /* end of try - except */

}



/***
*_amsg_exit(rterrnum) - Fast exit fatal errors
*
*Purpose:
*       Exit the program with error code of 255 and appropriate error
*       message.
*
*Entry:
*       int rterrnum - error message number (amsg_exit only).
*
*Exit:
*       Calls exit() (for integer divide-by-0) or _exit() indirectly
*       through _aexit_rtn [amsg_exit].
*       For multi-thread: calls _exit() function
*
*Exceptions:
*
*******************************************************************************/

void __cdecl _amsg_exit (
        int rterrnum
        )
{
#ifdef _WINMAIN_
        if ( __error_mode == _OUT_TO_STDERR )
#else  /* _WINMAIN_ */
        if ( __error_mode != _OUT_TO_MSGBOX )
#endif  /* _WINMAIN_ */
            _FF_MSGBANNER();    /* write run-time error banner */

        _NMSG_WRITE(rterrnum);  /* write message */
        _aexit_rtn(255);        /* normally _exit(255) */
}

/***
*fast_error_exit(rterrnum) - Faster exit fatal errors
*
*Purpose:
*       Exit the process with error code of 255 and appropriate error
*       message.
*
*Entry:
*       int rterrnum - error message number (amsg_exit only).
*
*Exit:
*       Calls ExitProcess.
*
*Exceptions:
*
*******************************************************************************/

static void __cdecl fast_error_exit (
        int rterrnum
        )
{
#ifdef _WINMAIN_
        if ( __error_mode == _OUT_TO_STDERR )
#else  /* _WINMAIN_ */
        if ( __error_mode != _OUT_TO_MSGBOX )
#endif  /* _WINMAIN_ */
            _FF_MSGBANNER();    /* write run-time error banner */

        _NMSG_WRITE(rterrnum);  /* write message */
        ExitProcess(255);       /* normally _exit(255) */
}

#ifndef WPRFLAG


#endif  /* WPRFLAG */

#endif  /* CRTDLL */

#else  /* _WIN32 */

#include <cruntime.h>
#include <internal.h>
#include <stdlib.h>
#include <msdos.h>
#include <string.h>
#include <setjmp.h>
#include <dbgint.h>
#include <macos\types.h>
#include <macos\segload.h>
#include <macos\gestalte.h>
#include <macos\osutils.h>
#include <macos\traps.h>
#include <mpw.h>

static void __cdecl Inherit(void);  /* local function */

int __cdecl main(int, char **, char **);             /*generated by compiler*/

unsigned long _GetShellStack(void);

static char * __cdecl _p2cstr_internal ( unsigned char * str );

extern MPWBLOCK * _pMPWBlock;
extern int __argc;
extern char **__argv;

/***
*__crt0()
*
*Purpose:
*       This routine does the C runtime initialization, calls main(), and
*       then exits.  It never returns.
*
*Entry:
*
*Exit:
*       This function never returns.
*
*******************************************************************************/

void __cdecl __crt0 (
        )
{
        int mainret;
        char szPgmName[32];
        char *pArg;
        char *argv[2];

#ifndef _M_MPPC
        void *pv;

        /* This is the magic stuff that MPW tools do to get info from MPW*/

        pv = (void *)*(int *)0x316;
        if (pv != NULL && !((int)pv & 1) && *(int *)pv == 'MPGM') {
            pv = (void *)*++(int *)pv;
            if (pv != NULL && *(short *)pv == 'SH') {
                _pMPWBlock = (MPWBLOCK *)pv;
            }
        }

#endif  /* _M_MPPC */

        _environ = NULL;
        if (_pMPWBlock == NULL) {
            __argc = 1;
            memcpy(szPgmName, (char *)0x910, sizeof(szPgmName));
            pArg = _p2cstr_internal(szPgmName);
            argv[0] = pArg;
            argv[1] = NULL;
            __argv = argv;

#ifndef _M_MPPC
            _shellStack = 0;                        /* force ExitToShell */
#endif  /* _M_MPPC */
        }
#ifndef _M_MPPC
        else {
            _shellStack = _GetShellStack();        //return current a6, or first a6
            _shellStack += 4;                      //a6 + 4 is the stack pointer we want
            __argc = _pMPWBlock->argc;
            __argv = _pMPWBlock->argv;

            Inherit();       /* Inherit file handles - env is set up by _envinit if needed */
        }
#endif  /* _M_MPPC */

        /*
         * call run time initializer
         */
        __cinit();

        mainret = main(__argc, __argv, _environ);
        exit(mainret);
}


#ifndef _M_MPPC
/***
*Inherit() - obtain and process info on inherited file handles.
*
*Purpose:
*
*       Locates and interprets MPW std files.  For files we just save the
*       file handles.   For the console we save the device table address so
*       we can do console I/O.  In the latter case, FDEV is set in the _osfile
*       array.
*
*Entry:
*       Address of MPW param table
*
*Exit:
*       No return value.
*
*Exceptions:
*
*******************************************************************************/

static void __cdecl Inherit (
        void
        )
{
        MPWFILE *pFile;
        int i;
        pFile = _pMPWBlock->pFile;
        if (pFile == NULL) {
            return;
        }
        for (i = 0; i < 3; i++) {
            switch ((pFile->pDevice)->name) {
                case 'ECON':
                    _osfile[i] |= FDEV | FOPEN;
                    _osfhnd[i] = (int)pFile;
                    break;

                case 'FSYS':
                    _osfile[i] |= FOPEN;
                    _osfhnd[i] = (*(pFile->ppFInfo))->ioRefNum;
                    break;
            }
            pFile++;
        }
}

#endif  /* _M_MPPC */



static char * __cdecl _p2cstr_internal (
        unsigned char * str
        )
{
        unsigned char *pchSrc;
        unsigned char *pchDst;
        int  cch;

        if ( str && *str ) {
            pchDst = str;
            pchSrc = str + 1;

            for ( cch=*pchDst; cch; --cch ) {
                *pchDst++ = *pchSrc++;
            }

            *pchDst = '\0';
        }

        return( str );
}

#endif  /* _WIN32 */

用到的宏

可以看到,里面使用了很多的宏,同时还涉及到一些例如wWinMainCRTStartup,WinMainCRTStartup,wmainCRTStartup以及我们现在需要查看的mainCRTStartup。根据不同的宏来选用不同的代码实现。

  1. _WIN32
  2. CRTDLL
  3. WPRFLAG
  4. _WINMAIN_
  5. _MT
  6. _M_MPPC
宏定义 说明
_WIN32 Defined for applications for Win32 and Win64. Always defined.
CRTDLL 未在官方MSDN上找到,应为MSVCRT.DLL的相关宏
WPRFLAG 未找到说明,应为unicode版的相关宏(默认为Multibyte)
_WINMAIN_ 未找到说明,应为windows窗体程序(默认为console程序)
_MT Defined when /MD or /MDd (Multithreaded DLL) or /MT or /MTd (Multithreaded) is specified.
_M_MPPC Defined for Power Macintosh platforms (no longer supported).

整理之后的代码

因为我们这里分析的是mainCRTStartup,用的是命令行程序,非unicode版本,单线程,非PPC。所以可以将对应的一些宏去掉,最终整理得到的代码(不包含辅助函数)为:

1 /***
2 *crt0.c - C runtime initialization routine
3 *
4 * Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
5 *
6 *Purpose:
7 * This the actual startup routine for apps. It calls the user's main
8 * routine [w]main() or [w]WinMain after performing C Run-Time Library
9 * initialization.
10 *
11 * (With ifdef's, this source file also provides the source code for
12 * wcrt0.c, the startup routine for console apps with wide characters,
13 * wincrt0.c, the startup routine for Windows apps, and wwincrt0.c,
14 * the startup routine for Windows apps with wide characters.)
15 *
16 *******************************************************************************/
17
18 #include <cruntime.h>
19 #include <dos.h>
20 #include <internal.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <rterr.h>
24 #include <windows.h>
25 #include <awint.h>
26 #include <tchar.h>
27 #include <dbgint.h>
28
29 _TUCHAR * __cdecl _wincmdln(void);
30
31  /*
32 * command line, environment, and a few other globals
33 */
34  char*_acmdln; /* points to command line */
35
36  char*_aenvptr = NULL; /* points to environment block */
37 wchar_t *_wenvptr = NULL; /* points to wide environment block */
38
39
40  void (__cdecl * _aexit_rtn)(int) = _exit; /* RT message return procedure */
41
42 staticvoid __cdecl fast_error_exit(int); /* Error exit via ExitProcess */
43
44 /*
45 * _error_mode and _apptype, together, determine how error messages are
46 * written out.
47 */
48 int __error_mode = _OUT_TO_DEFAULT;
49 int __app_type = _CONSOLE_APP;
50
51
52 /***
53 *BaseProcessStartup(PVOID Peb)
54 *
55 *Purpose:
56 * This routine does the C runtime initialization, calls main(), and
57 * then exits. It never returns.
58 *
59 *Entry:
60 * PVOID Peb - pointer to Win32 Process Environment Block (not used)
61 *
62 *Exit:
63 * This function never returns.
64 *
65 *******************************************************************************/
66
67 void mainCRTStartup(void)
68 {
69 int mainret;
70
71 /*
72 * Get the full Win32 version
73 */
74 _osver = GetVersion();
75
76 _winminor = (_osver >>8) &0x00FF ;
77 _winmajor = _osver &0x00FF ;
78 _winver = (_winmajor <<8) + _winminor;
79 _osver = (_osver >>16) &0x00FFFF ;
80
81 if ( !_heap_init(0) ) /* initialize heap */
82 fast_error_exit(_RT_HEAPINIT); /* write message and die */
83
84 /*
85 * Guard the remainder of the initialization code and the call
86 * to user's main, or WinMain, function in a __try/__except
87 * statement.
88 */
89
90 __try {
91
92 _ioinit(); /* initialize lowio */
93
94 /* get cmd line info */
95 _acmdln = (char*)GetCommandLineA();
96
97 /* get environ info */
98 _aenvptr = (char*)__crtGetEnvironmentStringsA();
99
100 _setargv();
101 _setenvp();
102
103 _cinit(); /* do C data initialize */
104
105 __initenv = _environ;
106 mainret = main(__argc, __argv, _environ);
107
108 exit(mainret);
109 }
110 __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
111 {
112 /*
113 * Should never reach here
114 */
115 _exit( GetExceptionCode() );
116
117 } /* end of try - except */
118 }

这样,就可以分析一下得到的代码了。同时,从代码中可以看到微软的编码风格,学习到一部分。

在mainCRTStartup()函数中,主要调用了GetVersion, _heap_init, fast_error_exit, _ioinit, GetCommandLineA, __crtGetEnvironmentStringsA, _setargv, _setenvp, _cinit, exit,以及最重要的main函数。

其中fast_error_exit位于crt0.c中,实现如下

1 staticvoid __cdecl fast_error_exit (
2 int rterrnum
3 )
4 {
5 #ifdef _WINMAIN_
6 if ( __error_mode == _OUT_TO_STDERR )
7 #else /* _WINMAIN_ */
8 if ( __error_mode != _OUT_TO_MSGBOX )
9 #endif /* _WINMAIN_ */
10 _FF_MSGBANNER(); /* write run-time error banner */
11
12 _NMSG_WRITE(rterrnum); /* write message */
13 ExitProcess(255); /* normally _exit(255) */
14 }

其他一些函数所在的文件或者说明如下,感兴趣的同学可以自行研究,这里因为篇幅原因,就不一一展开了。

函数名 文件 说明
GetVersion win32 API  
_heap_init Heapinit.c  
_ioinit Ioinit.c  
GetCommandLineA win32 API  
__crtGetEnvironmentStringsA A_env.c  
_setargv Setargv.c  
_setenvp Stdenvp.c  
_cinit Crt0dat.c  
exit Crt0dat.c  

由此可见,mainCRTStartup为main函数做了一些初始化的工作,包括创建heap,初始化low IO,以及命令行参数和环境变量的获取,和初始化C runtime Data。

至于内部的实现细节,因为时间关系,还没有具体去研究,暂时就先放在这里,等研究有一些心得再加到这里来。

当没有做这些初始化工作,而直接使用main作为入口函数的话,是会出现很多问题的,具体在下一篇写吧。

由于涉及到的这部分,网络上面的参考较少,自己揣摩出来居多,有错误难免,希望大家批评指正。

参考:

  1. http://www.a3gs.com/BookViews.asp?InfoID=2629&ClassID=819
  2. http://msdn.microsoft.com/en-us/library/b0084kay(v=VS.71).aspx
  3. http://msdn.microsoft.com/en-us/library/f9t8842e(v=vs.71).aspx

posted on 2011-06-14 20:30  cnyao  阅读(2992)  评论(4编辑  收藏  举报