一个进程通常是一个运行的程序的实例,它由两个部分构成: 1. 一个内核对象,操作系统用它管理进程,并保存进程的统计信息。 2. 一个地址空间,它包含所有的可执行或DLL模块的代码和数据,也包含了动态分配的内存如栈和堆等。
一个进程要能完成某些功能,它必须要有一个线程运行,该线程负责执行包含在进程地址空间听代码。一个进程可以包含多个线程,它们可以同时运行,每个线程都有自己的CPU寄存器组和自己的堆栈。当一个进程创建时,系统会自己创建它的第一个线程,称为基本线程(primary thread)。这个线程可以创建另外的线程。如果进程地址空间中没有线程运行,系统自动销毁进程及其地址空间。一. 编写第一个Windows程序 Windows程序分成GUI和CUI,后者虽然也包含在窗口中,但窗口只能包含文本,如CMD.EXE。用vs创建程序时,链接器使用/SUBSYSTEM来决定程序类型。生成的类型信息会放在执行映像的头部。当系统加载执行映像时,它会查找这个信息,如果是控制台程序,系统会启动一个控制台窗口。而如果是GUI程序,则系统不会启动控制台窗口而只是加载该映像。一旦程序启动,系统便不会关心程序类型了。 Windows程序的进入点函数有下面两个:
int WINAPI _tWinMain
(
HINSTANCE hInstanceExe,
HINSTANCE,
PTSTR pszCmdLine,
int nCmdShow
);
int _tmain
(
int argc,
TCHAR *argv[],
TCHAR *envp[]
);
但是操作系统不会调用你写的进入点函数!它是调用C/C++启动函数,后者由链接时的 -entry:XXX设置。这个函数会初始化C/C++运行库,这样你可以调用如malloc或free等,你还确保你声明的任何全局或静态C++对象在代码退出时被销毁。这些函数:WinMainCRTStartup,wWinMainCRTStartup,mainCRTStartup, wmainCRTStartup等(加w的是宽字符版本)。这四个函数在crtexe.c文件中可以找到。这些启动函数完成如下功能:
1. 取得新进程的全部命令行参数的指针
2. 取得新进程的环境变量的指针
3. 初始化C/C++运行时全局变量。你的代码如果包含StdLib.h就可以访问这些变量,如_osver, _winmajor, _winminor, _winver, __argc, __argv, __wargv, _environ, _wenviron, _pgmptr, _wpgmptr等。
4. 用C进行时函数malloct和calloc和底层I/O函数初始化堆
5. 为所有全局和静态C++类对象调用构造函数。当然这些初始化完成后便调用你的启动函数。代码如下(UNICODE版本): GetStartupInfo(&StartupInfo); int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT); int nMainRetVal = wmain(argc, argv, envp); 如果要在_tmain的入口访问环境变量,则_tmain应写成 int _tmain(int argc, TCHAR* argv[], TCHAR* env[])
当你的进入点函数退出时,启动函数调用C运行时的exit函数,它传递返回值能它并完成如下功能:
1. 调用由_onexit函数调用的任何函数
2. 为所有全局和静态C++类对象调用析构函数
3. 如果以DEBUG方式生成,则C/C++运行时内存的任何泄漏都会由 _CrtDumpMemoryLeaks函数列出
4. 调用操作系统的ExitProcess函数,把返回值传递给它。
但是由于安全原因,这些变量都已经变成过时的,你应该调用相应的Windows API函数来获取这些变量。
二. 进程实例句柄装载到进程地址空间的每个可执行的或DLL文件都会被赋予一个唯一的实例句柄,你的可执行文件的实例是作为WinMain的第一个参数传递hInstance,该句柄值通常用于装载资源等,如 HICON LoadIcon(HINSTANCE hInstance, PCTSTR pszIcon); 在上面的调用中,第一个参数就表示哪个文件(哪个DLL)包含了你要加载的资源。许多程序把hInstance保存作为一个全局变量。 SDK文档中有些函数需要HMODULE类型的参数,实际上它们是一样的。如 DWORD GetModuleFileName( HMODULE hInstModule, PTSTR pszPath, DWORD cchPath); hInstance的真实值是可执行文件映像装载的基址,vs链接器默认是0x00400000,你可通过/BASE:address进行更改。函数GetModuleHandle可取得一个可执行或DLL文件的装载句柄,它用文件名作为参数,如果参数是NULL,则返回调用者的句柄。但如果代码中DLL中运行,要获取句柄有两种办法:
1. 使用链接器提供的__ImageBase伪变量(C运行时代码就以这种方式)
2. 调用GetModuleHandleEx
下面是这些代码:
#include<stdio.h>
#include<windows.h>
extern "C" const IMAGE_DOS_HEADER __ImageBase;
void DumpModule()
{
HMODULE hModule = GetModuleHandle(NULL);
printf("用GetModuleHandle(NULL) = 0x%x\r\n", hModule);
printf("用__ImageBase = 0x%x\r\n", (HINSTANCE)&__ImageBase);
hModule = NULL;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(PCTSTR)DumpModule, &hModule);
printf("用GetModuleHandleEx = 0x%x\r\n", hModule);
}
int main(int argc, TCHAR* argv[])
{
DumpModule();
return 0;
}
上面代码可以看出,不管在哪调用GetModuleHandle,它总返回可执行文件的基址。另外,在生成的WinMain函数的第二个参数也是HINSTANCE,它是指前一程序的实例句柄,这个参数现在不应该使用。