进程由两部分组成:
进程是不活泼的,进程当中至少要有一个线程,每个线程要有自己的堆栈和自己的CPU寄存器。CPU通过算法给每个线程分配时间片的办法来造成假象是在同时工作(多核通过自己的算法实现同时运行)。
4.1 编写第一个Windiws应用程序
Windows两种类型的程序:
注意:俩者的概念其实是很模糊的,CUI可以创建GUI图形界面,反之GUI程序可能用CUI程序。
Windows进入点函数(区分在于CUI和GUI程序,ANSI码和UNICODE码)
int WINAPI WinMain( HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow); int WINAPI wWinMain( HINSTANCE hinstExe, HINSTANCE, PWSTR pszCmdLine, int nCmdShow); int __cdecl main( int argc, char *argv[], char *envp[]); int __cdecl wmain( int argc, wchar_t *argv[], wchar_t *envp[]);
其实Windows程序启动时最开始并不调用自己写的入口函数,而是调用系统的几个入口函数,以便可以调用malloc和free之类的函数,初始化全局和静态C++对象等。
应用程序类型 进入点 嵌入可执行文件的启动函数
ANSI码GUI应用程序 WinMain WinMainCRTStattup
UNICODE码GUI应用程序 wWinMain wWinMainCRTStattup
ANSI码CUI应用程序 main mainCRTStattup
UNICODE码CUI应用程序 wmain wmainCRTStattup
注意:应用程序会根据SUBSYSTEM开关来查找嵌入可执行启动函数,如果进入点函数和启动函数不匹配则显示链接错误。可以删除SUBSYSTEM(VS Project Settings)开关,这样应用程序会自动需找匹配的函数。
进入点函数返回时调用系统的exit函数,将返回值传递给它。exit函数负责下面操作:
4.1.1 进程的实例句柄
WinMain/wWinMain函数的第一个参数表示进程加载的可执行文件的基地址/句柄。对于加载资源的调用都要使用此句柄,比如HICON LoadIcon(HINSTANCE, PCTSTR)。有的函数需要使用HMODULE,和HINSTANCE是一个意思(区分主要在于16位的操作系统中)。
HMODULE GetModuleHandle(PCTSTR pszModele);
函数作用,返回加载调用进程中的可执行文件或者DLL的基地址/句柄,参数是可执行文件或者DLL的名称。给pszModule赋值NULL,则返回的是进程中可执行文件的句柄。
注意:如果找不到则返回NULL。如果在DLL中传递NULL,返回的仍然是进程加载的可执行文件的句柄。
4.1.2 进程的前一个实例句柄
第二个参数都传递NULL,是为16位系统所保留的。
4.1.3 进程的命令行
注意:不要试图修改命令行内部内存的值,要使用修改先拷贝出来。
PTSTR GetCommandLine(); // 返回命令行字符串
PTSTR CommandLineToArgv(PTSTR pszCmdLine, int *pNumArgs); // 拆分命令行字符串函数
Demo:
int nNumargs; PTSTR *ppArgv = CommandLineToArgv(GetCommandLine(), &nNumargs); if ('x' == *ppArgv[1]) { // TODO: } // 手动释放内存,一般不需要释放,系统会进程关闭时候自动释放 HeapFree(GetProcessHeap, 0, ppArgv);
4.1.4 进程的环境变量
环境块是进程地址空间中分配的内存块每个环境块都包含一组字符串,格式如下:
VarName1=VarVarlue1\0
VarName2=VarVarlue2\0
VarName3=VarVarlue3\0
…..
VarNameX=VarVarlueX\0
\0
注意:
DWORD GetEnvironmentVariable(PCTSTR pszName, PTSTR pszValue, DWORD cchValue);
pszName指变量名,pszValue指向变量值的缓存区,cchValue缓存区的大小。找不到变量名或者设置的长度不够存放就返回0。
ExpandEnvironmentStrings(PCSTR pszSrc, PSTR pszDst, DWORD nSize);
用来用现实出可替换的环境变量的字符串。
BOOL SetEnvironmentVariable(PCTSTR pszName, PCTSTR pszValue);
设置环境变量的值,如果不存在则创建,如果存在则替换他的值。
4.1.5 进程的亲缘性
子进程继承父进程的亲缘性。(具体什么意思没明白)
4.1.6 进程的错误模式
进程可以设置如何处理一些错误。
UINT SetErrorMode(UINT fuErrorMode);
各个模式用OR连接
标志 说明
SEM_FAILCRITICALERRORS 系统不显示关键错误句柄消息框,并将错误返回给调用进程
SEM_NOGOFAULTERRORBOX 系统不显示一般保护故障消息框。本标志只应该由采用异常情况处理程序来处理一般保护(GP)故障的调式应用程式来设定
SEM_NOOPENFILEERRORBOX 当系统找不到文件时,它不显示消息框。
SEM_NOALIGNMENTFAULTEXCEPT 系统自动排除内存没有对其的故障,并使应用程序看不到这些故障。本标志对X86处理器不起作用。
子进程继承父进程的错误模式,如果不想让子进程继承父进程的错误模式的话,可以再调用CreateProcess时设定CREATE_DEFAULT_ERROR_MODE标志。
4.1.7 进程的当前驱动器和目录
默认情况下不提供全路径的话,系统就会在当前驱动器和目录中查找文件,比如CreateFile,因为驱动器和目录是每个进程来维护的,所以某个线程改变了目录和驱动器会改变整个进程的目录和驱动器。
下面两个函数读取和设置:
DWORD GetCurrentDirectory(DWORD cchCurDir, PTSTR pszCurDir); BOOL SetCurrentDirectory(PCTSTR pszCurDir);
4.1.8 进程的当前目录
驱动器环境块的格式:
=C:=C:\Utility\Bin
程序查找驱动器环境块,如果没有则按驱动器名查找。
子进程不能继承父进程的驱动器块,如果想继承必须写到环境变量中去。(好像是这样,如果有不对请高人指点)。
DWORD GetFullPathName(PCTSTR pszFile, DWORD cchPath, PTSTR pszPath, PTSTR *ppszFilePart);
获取驱动器的当前目录,比如:
TCHAR szCurDir[MAX_PATH];
DWORD GetFullPathName(TEXT("C:"), MAX_PATH, szCurDir, NULL);
4.1.9 系统版本
DWORD GetVersion();此函数存在高地位的混论BUG,所以尽量不要使用。
BOOL GetVersion(POSVERSIONINFOEX pVersionInfomation); typedef struct _OSVERSIONINFOEXA { DWORD dwOSVersionInfoSize; // 在调用GetVersionEx函数之前,必必须置为sizeof(OSVERSIONINFOEX) DWORD dwMajorVersion; // 主系统的主要版本号 DWORD dwMinorVersion; // 主系统的次要版本号 DWORD dwBuildNumber; // 当前系统的构建号 DWORD dwPlatformId; // 识别当前系统的平台。可以使VER_PLATFORM_WIN32(WIN32),VER_PLATFORM_WIN32_WINDOWS(WINDOWS 95/WINDOWS 98),VER_PLATFORM_WIN32_NT(WINDOWS NT/WINDOWS 2000)或VER_PLATFORM_WIN32_CEHH(WINDOWS CE) CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage 本域包含了附加文本,用于提供关于已经安装的操作系统的详细信息 WORD wServicePackMajor; // 最新安装的服务程序包的主要版本号 WORD wServicePackMinor; // 最新安装的服务程序包的次要版本号 WORD wSuiteMask; // 用于标识系统上存在那个程序组(VER_SUITE_SMALLBUSINESS,VER_SUITE_ENTERPRISE,VER_SUITE_BACKOFFICE,VER_SUITE_COMMUNICATIONS,VER_SUITE_TERMINAL,VER_SUITE_SMALLBUSINESS_RESTRICTED,VER_SUITE_EMBEDDEDNT和VER_SUITE_DATACENTER) BYTE wProductType; // 用于标识安装了下面的哪个操作系统:VER_NT_WORKSTATION,VER_NT_SERVER或VER_NT_DOMAIN_CONTROLLER BYTE wReserved; // 留作将来使用 } OSVERSIONINFOEXA, *POSVERSIONINFOEXA, *LPOSVERSIONINFOEXA;
BOOL CreateProcess( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
4.2.1 pszApplicationName和pszCommandLine
pszCommandLine参数:用来创建进程的命令行参数。查看第一个标记,如果没有”.exe”会自动添加”.exe”上去。(如果pszApplicationName参数NULL)
如果pszApplicationName参数不为NULL,系统将在当前目录中查找.exe文件(不会自动添加“.exe”),如果找不到将失败,此时pszCommandLine作为参数传递给可执行程序的进程。
4.2.2 psaProcess,psaThread和binHeritHandles
psaProcess,psaThread是进程和进程主线程内核对象的安全属性。默认值为NULL。
binHeritHandles设置为TRUE表示父进程在创建子进程可以继承安全属性标志里设置为TRUE的任何可继承的内核对象。如果设置为FALSE子进程将不继承任何内核对象。
4.2.3 fdwCreate
用于标识标志,定义规则如何创建新进程。我一般写默认值NULL。具体的太多了,请查看MSDN吧,不想写了。
4.2.4 pvEnvironment
设置子进程使用的环境内存块,一般默认值为NULL,表示子进程继承父进程的环境块。
PVOID GetEnvironmentString(); // 获取当前内存块的地址 BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock); // 不用的时候调用此函数释放内存块
4.2.5 pszCurDir
设定工作目录和驱动器号,如果为NULL则和应用程序的目录相同,如果设置比如以’\0’结尾的包含驱动器名的路径。
4.2.6 psiStartInfo
typedef struct _STARTUPINFO { DWORD cb; //(两者兼有,控制台和窗口程序) LPSTR lpReserved; // (两者兼有)保留,必须初始化为NULL LPSTR lpDesktop; // (两者兼有)用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。如果lpDesktop是NULL(这是最常见的情况),那么该进程将与当前桌面相关联。 LPSTR lpTitle; // (控制台)用于设定控制台窗口名称。如果lpTitle是NULL,则可执行文件的名字将用作窗口名 DWORD dwX; // x,y坐标,只有当子进程用CW_USEDEFAULT作为CreateWindows的x参数来创建它的第一个重叠窗口时,才使用这两个坐标。若是创建控制台窗口的应用程序,这些成员用于指明控制台窗口的左上角。 DWORD dwY; DWORD dwXSize; //(两者兼有)设定窗口宽度和长度,只有子进程用WM_USEDEFAULT作为CreateWIndows的nWidth参数来创建它的第一个重叠窗口时才是用这个值。控制台就是控制台的宽和长 DWORD dwYSize; DWORD dwXCountChars; //(控制台)用于设定子应用程序控制台的长度和宽度(字符表示) DWORD dwYCountChars; DWORD dwFillAttribute;// (控制台)用于设定子应用程序的控制台背影颜色和文本。 DWORD dwFlags; // (两者兼有)参见下一段 WORD wShowWindow; // (窗口)用于设定子应用程序初次调用ShowWindow将SW_SHOWDEFAULT作为nCmdShow参数传递时,该应用程序的第一个重叠窗口应该如何出现。本成员可以是通常用于ShowWindow函数的任何一个SW_*标识符 WORD cbReserved2; // 保留,必须初始化为0 LPBYTE lpReserved2; // 保留,必须初始化为NULL HANDLE hStdInput; // (控制台)用于设定控制台输入和输出用的缓存的句柄。默认设置hStdInput是键盘缓存,hStdOutput和hStdError窗口的缓存。 HANDLE hStdOutput; // HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
设置某些值,大部分需要默认值,必须初始化为0都。
STARTUPINFO si = {sizeof(si)};
dwFlags标志,用于修改如何来创建子进程。
标志
STARTF_USESIZE 使用dwXSize和dwYSize成员
STARTF_USESHOWWINDOW 使用wShowWIndow成员
STARTF_USEPOSITION 使用dwX和dwY成员
STARTF_USECOUNTCHARS 使用dwXCountChars和dwYCountChars成员
STARTF_USEFILLATTRIBUTE 使用dwFillAttribute成员
STARTF_USESTDHANDLES 使用hStdInput,hStdOutput和hStdError成员
STARTF_RUN_FULLSCREEN 强制再x86计算机上运行的控制台应用程序以全屏幕方式启动运行
STARTF_FORCEONFEEDBACK 光标设置为沙漏,过了2秒如果进程没启动GUI,CreateProcess程序将光标设置为箭头,5秒内显示一个窗口,成功调用GetMessage则反复箭头,如果没有成功,等待5秒 变为箭头
STARTF_FORCEOFFFEEDBACK
4.2.7 ppiProcInfo
typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
返回的分别是,子进程进程句柄,子进程中主线程的线程句柄,子进程ID,子进程中主线程的ID。
注意:
4.3 终止进程的运行
4.3.1 主线程的进入点函数返回
最好强力推荐使用这种方式。
4.3.2 ExitProcess函数
避免使用这个方法。
VOID ExitProcess(UINT fuExitCode);
终止进程运行,并将退出码设置为fuExitCode。
注意:
4.3.3 TerminiateProcess函数
能不用就别用。
BOOL TerminiateProcess(HANDLE hProcess, UINT fuExitCode;)
关闭指定为hProcess句柄的进程,推出代码为fuExitCode。
注意:
4.3.4 进程终止运行时出现的情况
注意:
进程内核对象的寿命可能远远大于进程本身,父进程保留子进程内核对象可以查看它的推出代码调用下面函数:
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
可以调用这个函数来判断子进程是否关闭,如果子进程没有关闭,它的STILL_ACTIVE标识符定义为0x103。但是这么作效率不是很高。
4.4 子进程
没什么好说的,前面的都说了,用CloseHandle关闭子进程和子进程中主线程的句柄值来切断父进程和子进程的所有联系。
4.5 每局系统中运行的进程
利用ToolHelp函数族来开发管理操作系统上的进程。打算自己也写个试试。
本文章的内容是本人学习Windows核心编程第四章后的总结,有错误请大家纠正,转载注明出处: