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

从上一篇文章中,大家已经了解到有C运行时库这个概念,这个不算是新东西,但是一般都隐藏在幕后,C/C++语言教学的时候不讲,windows/linux编程的时候似乎也不会专门讲到。不过它一般是我们C/C++编程中默认会使用的一个重要部分。回想想,我们随手打出的strcpy, memset, memcpy等等,不就是C运行时库所提供出来的东西吗?
既然这样,就要好好研究一下这个东西了。
前面已经说过,针对单线程/多线程,静态/动态链接,是否是debug版本,VC6的C运行时库提供了6个版本。具体可以看下面的截图。
image从零开始,学习windows编程(4)--从libc.lib开始从零开始学编程
而其中每一个选择对应的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 #include #include #include #include #include #include #include #include #include /* * 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 _disibledevent=> #include #include #include #include #include #include #include #include #include #include #include #include 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 _disibledevent=>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。所以可以将对应的一些宏去掉,最终整理得到的代码(不包含辅助函数)为:
/*** *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.) * *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include _TUCHAR * __cdecl _wincmdln(void); /* * command line, environment, and a few other globals */ char *_acmdln; /* points to command line */ 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; int __app_type = _CONSOLE_APP; /*** *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. * *******************************************************************************/ void mainCRTStartup(void) { int mainret; /* * Get the full Win32 version */ _osver = GetVersion(); _winminor = (_osver >> 8) & 0x00FF ; _winmajor = _osver & 0x00FF ; _winver = (_winmajor << 8) + _winminor; _osver = (_osver >> 16) & 0x00FFFF ; if ( !_heap_init(0) ) /* initialize heap */ fast_error_exit(_RT_HEAPINIT); /* write message and die */ /* * 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 */ /* get cmd line info */ _acmdln = (char *)GetCommandLineA(); /* get environ info */ _aenvptr = (char *)__crtGetEnvironmentStringsA(); _setargv(); _setenvp(); _cinit(); /* do C data initialize */ __initenv = _environ; mainret = main(__argc, __argv, _environ); exit(mainret); } __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) { /* * Should never reach here */ _exit( GetExceptionCode() ); } /* end of try - except */ }
这样,就可以分析一下得到的代码了。
在mainCRTStartup()函数中,主要调用了GetVersion, _heap_init, fast_error_exit, _ioinit, GetCommandLineA, __crtGetEnvironmentStringsA, _setargv, _setenvp, _cinit, exit,以及最重要的main函数。
其中fast_error_exit位于crt0.c中,实现如下
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) */ }
其他一些函数所在的文件或者说明如下,感兴趣的同学可以自行研究,这里因为篇幅原因,就不一一展开了。
函数名 文件 说明
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
Tags:  网游之从零开始 从零开始最新章节 从零开始学英语 从零开始 从零开始学编程

延伸阅读

最新评论

发表评论