进程由两部分组成:
- 操作系统管理进程的内核对象。存放该进程 的统计信息的地方。
- 地址空间,包含可执行模块和DLL模块的代码和数据。动态分配的内存(线程堆栈和堆)。
进程是不活泼的,进程当中至少要有一个线程,每个线程要有自己的堆栈和自己的CPU寄存器。CPU通过算法给每个线程分配时间片的办法来造成假象是在同时工作(多核通过自己的算法实现同时运行)。
4.1 编写第一个Windiws应用程序
Windows两种类型的程序:
- CUI程序,比如CMD.EXE等等。Microsoft Visual C++连接开关为/SUBSYSTEM:CONDOLE(程序启动时不能创建GUI程序)。
- GUI程序,图形用户程序,比如Notepad,Word等等。Microsoft Visual C++连接开关为/SUBSYSTEM:WINDOWS(程序启动时不能创建CUI程序)。
注意:俩者的概念其实是很模糊的,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++对象等。
- 检索指向新进程的完整命令行的指针。
- 检索指向新进程的环境变量的指针。
- 对C/C++运行期的全局变量进行初始化。如果包含StdLib.h文件,代码就可以访问这些变量。
- 对C运行期的malloc和callo和其他底层输入/输出例程使用的内存栈进行初始化。
- 为所有全局和静态C++类对象调用构造函数。
应用程序类型 进入点 嵌入可执行文件的启动函数
ANSI码GUI应用程序 WinMain WinMainCRTStattup
UNICODE码GUI应用程序 wWinMain wWinMainCRTStattup
ANSI码CUI应用程序 main mainCRTStattup
UNICODE码CUI应用程序 wmain wmainCRTStattup
注意:应用程序会根据SUBSYSTEM开关来查找嵌入可执行启动函数,如果进入点函数和启动函数不匹配则显示链接错误。可以删除SUBSYSTEM(VS Project Settings)开关,这样应用程序会自动需找匹配的函数。
进入点函数返回时调用系统的exit函数,将返回值传递给它。exit函数负责下面操作:
- 调用由_onexit函数的调用而注册的任何函数。
- 为所有全局的和静态的C++类对象调用析构函数
- 调用操作系统的ExitProcess,并将返回值传递给他,关闭进程。
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
注意:
- 排序必须按照字母顺序。
- ‘=’号不能是变量名的一部分。
- 等号左右两边的空格将被算做名称或者值。
- 最后必须加个’\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;
这个是扩展版本。
4.2 CreateProcess函数
终于看到正题了~
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
);
- 进程启动时,首先创建一个进程内核对象,该内核对象不是进程,是一个管理进程存储进程信息的小型数据结构。(计数为1)
- 创建一个虚拟地址空间,加载可执行文件和DLL。
- 为进程创建个主线程的内核对象,和进程内核对象一样,是用来管理和存储线程信息的小型数据结构。(计数为1)
- 调用C/C++运行期启动代码,主线程开始运行,最终调用启动函数,成功返回TRUE(未能正确加载DLL也返回TRUE,所以父进程无法查看)。
4.2.1 pszApplicationName和pszCommandLine
pszCommandLine参数:用来创建进程的命令行参数。查看第一个标记,如果没有”.exe”会自动添加”.exe”上去。(如果pszApplicationName参数NULL)
- 包含调用进程的”.exe”文件的目录。
- 调用进程的当前目录。
- windows系统目录
- windows目录
- PATH环境变量中列出的目录。
如果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。
注意:
- hProcess和hThread被赋值后,内核对象的计数器分别被+1。
- 系统为每个进程和线程分配的ID值都是不同的,但是当某进程退出后,新进程很可能会使用退出进程的ID。
4.3 终止进程的运行
4.3.1 主线程的进入点函数返回
最好强力推荐使用这种方式。
- 调用C++析构函数。
- 释放堆栈内存。
- 将进程退出代码(进程内核对象中维护)设置为进入点函数返回值。
- 系统将进程内核对象的返回值减去1。
4.3.2 ExitProcess函数
避免使用这个方法。
VOID ExitProcess(UINT fuExitCode);
终止进程运行,并将退出码设置为fuExitCode。
注意:
- 调用ExitProcess后,所有的代码都将不会执行,关闭进程。
- 调用ExitProcess后,不会释放C++析构函数资源,有系统清理进程时候直接释放内存。
- 进程的主线程直接return退出后,启动函数中也会调用ExitProcess函数,进程终止,其他线程也会关闭。
- 进程的主线程调用_endThreadex或者EndThread函数关闭主线程后,进程没有被关闭,子线程继续运行。
4.3.3 TerminiateProcess函数
能不用就别用。
BOOL TerminiateProcess(HANDLE hProcess, UINT fuExitCode;)
关闭指定为hProcess句柄的进程,推出代码为fuExitCode。
注意:
- 关闭进程将丢失所有需要保存到硬盘的数据,因为进程关闭时候最后会自己释放内存资源,关闭内核对象的句柄值,所以不会造成内存泄露。
- 此函数是个异步函数,它返回的时候无法知道,需要关闭的进程是否已经被强制关闭了。
4.3.4 进程终止运行时出现的情况
- 进程中剩余的所有线程全部终止运行。
- 释放该进程引用的GDI和用户对象,内核对象被关闭(别的进程有引用则计数器减1,如果没有引用则关闭内核对象)。
- 推出代码将从STILL_ACTIVE(后面章节线程将介绍该结构)改为传递给ExitProcess和TerminiateProcess代码。
- 内核对象状态变为收到通知状态(线程中介绍),其他线程挂起,知道进程终止。
- 进程内核对象计数减去1,或者关闭。
注意:
进程内核对象的寿命可能远远大于进程本身,父进程保留子进程内核对象可以查看它的推出代码调用下面函数:
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
可以调用这个函数来判断子进程是否关闭,如果子进程没有关闭,它的STILL_ACTIVE标识符定义为0x103。但是这么作效率不是很高。
4.4 子进程
没什么好说的,前面的都说了,用CloseHandle关闭子进程和子进程中主线程的句柄值来切断父进程和子进程的所有联系。
4.5 每局系统中运行的进程
利用ToolHelp函数族来开发管理操作系统上的进程。打算自己也写个试试。
本文章的内容是本人学习Windows核心编程第四章后的总结,有错误请大家纠正,转载注明出处:
http://www.cnblogs.com/xi52qian/
进程由两部分组成:
- 操作系统管理进程的内核对象。存放该进程 的统计信息的地方。
- 地址空间,包含可执行模块和DLL模块的代码和数据。动态分配的内存(线程堆栈和堆)。
进程是不活泼的,进程当中至少要有一个线程,每个线程要有自己的堆栈和自己的CPU寄存器。CPU通过算法给每个线程分配时间片的办法来造成假象是在同时工作(多核通过自己的算法实现同时运行)。
4.1 编写第一个Windiws应用程序
Windows两种类型的程序:
- CUI程序,比如CMD.EXE等等。Microsoft Visual C++连接开关为/SUBSYSTEM:CONDOLE(程序启动时不能创建GUI程序)。
- GUI程序,图形用户程序,比如Notepad,Word等等。Microsoft Visual C++连接开关为/SUBSYSTEM:WINDOWS(程序启动时不能创建CUI程序)。
注意:俩者的概念其实是很模糊的,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++对象等。
- 检索指向新进程的完整命令行的指针。
- 检索指向新进程的环境变量的指针。
- 对C/C++运行期的全局变量进行初始化。如果包含StdLib.h文件,代码就可以访问这些变量。
- 对C运行期的malloc和callo和其他底层输入/输出例程使用的内存栈进行初始化。
- 为所有全局和静态C++类对象调用构造函数。
应用程序类型 进入点 嵌入可执行文件的启动函数
ANSI码GUI应用程序 WinMain WinMainCRTStattup
UNICODE码GUI应用程序 wWinMain wWinMainCRTStattup
ANSI码CUI应用程序 main mainCRTStattup
UNICODE码CUI应用程序 wmain wmainCRTStattup
注意:应用程序会根据SUBSYSTEM开关来查找嵌入可执行启动函数,如果进入点函数和启动函数不匹配则显示链接错误。可以删除SUBSYSTEM(VS Project Settings)开关,这样应用程序会自动需找匹配的函数。
进入点函数返回时调用系统的exit函数,将返回值传递给它。exit函数负责下面操作:
- 调用由_onexit函数的调用而注册的任何函数。
- 为所有全局的和静态的C++类对象调用析构函数
- 调用操作系统的ExitProcess,并将返回值传递给他,关闭进程。
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
注意:
- 排序必须按照字母顺序。
- ‘=’号不能是变量名的一部分。
- 等号左右两边的空格将被算做名称或者值。
- 最后必须加个’\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;
这个是扩展版本。
4.2 CreateProcess函数
终于看到正题了~
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
);
- 进程启动时,首先创建一个进程内核对象,该内核对象不是进程,是一个管理进程存储进程信息的小型数据结构。(计数为1)
- 创建一个虚拟地址空间,加载可执行文件和DLL。
- 为进程创建个主线程的内核对象,和进程内核对象一样,是用来管理和存储线程信息的小型数据结构。(计数为1)
- 调用C/C++运行期启动代码,主线程开始运行,最终调用启动函数,成功返回TRUE(未能正确加载DLL也返回TRUE,所以父进程无法查看)。
4.2.1 pszApplicationName和pszCommandLine
pszCommandLine参数:用来创建进程的命令行参数。查看第一个标记,如果没有”.exe”会自动添加”.exe”上去。(如果pszApplicationName参数NULL)
- 包含调用进程的”.exe”文件的目录。
- 调用进程的当前目录。
- windows系统目录
- windows目录
- PATH环境变量中列出的目录。
如果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。
注意:
- hProcess和hThread被赋值后,内核对象的计数器分别被+1。
- 系统为每个进程和线程分配的ID值都是不同的,但是当某进程退出后,新进程很可能会使用退出进程的ID。
4.3 终止进程的运行
4.3.1 主线程的进入点函数返回
最好强力推荐使用这种方式。
- 调用C++析构函数。
- 释放堆栈内存。
- 将进程退出代码(进程内核对象中维护)设置为进入点函数返回值。
- 系统将进程内核对象的返回值减去1。
4.3.2 ExitProcess函数
避免使用这个方法。
VOID ExitProcess(UINT fuExitCode);
终止进程运行,并将退出码设置为fuExitCode。
注意:
- 调用ExitProcess后,所有的代码都将不会执行,关闭进程。
- 调用ExitProcess后,不会释放C++析构函数资源,有系统清理进程时候直接释放内存。
- 进程的主线程直接return退出后,启动函数中也会调用ExitProcess函数,进程终止,其他线程也会关闭。
- 进程的主线程调用_endThreadex或者EndThread函数关闭主线程后,进程没有被关闭,子线程继续运行。
4.3.3 TerminiateProcess函数
能不用就别用。
BOOL TerminiateProcess(HANDLE hProcess, UINT fuExitCode;)
关闭指定为hProcess句柄的进程,推出代码为fuExitCode。
注意:
- 关闭进程将丢失所有需要保存到硬盘的数据,因为进程关闭时候最后会自己释放内存资源,关闭内核对象的句柄值,所以不会造成内存泄露。
- 此函数是个异步函数,它返回的时候无法知道,需要关闭的进程是否已经被强制关闭了。
4.3.4 进程终止运行时出现的情况
- 进程中剩余的所有线程全部终止运行。
- 释放该进程引用的GDI和用户对象,内核对象被关闭(别的进程有引用则计数器减1,如果没有引用则关闭内核对象)。
- 推出代码将从STILL_ACTIVE(后面章节线程将介绍该结构)改为传递给ExitProcess和TerminiateProcess代码。
- 内核对象状态变为收到通知状态(线程中介绍),其他线程挂起,知道进程终止。
- 进程内核对象计数减去1,或者关闭。
注意:
进程内核对象的寿命可能远远大于进程本身,父进程保留子进程内核对象可以查看它的推出代码调用下面函数:
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
可以调用这个函数来判断子进程是否关闭,如果子进程没有关闭,它的STILL_ACTIVE标识符定义为0x103。但是这么作效率不是很高。
4.4 子进程
没什么好说的,前面的都说了,用CloseHandle关闭子进程和子进程中主线程的句柄值来切断父进程和子进程的所有联系。
4.5 每局系统中运行的进程
利用ToolHelp函数族来开发管理操作系统上的进程。打算自己也写个试试。
本文章的内容是本人学习Windows核心编程第四章后的总结,有错误请大家纠正,转载注明出处:
http://www.cnblogs.com/xi52qian/
头文件 <iostream>
一. 对终端的操作
相关头文件#include <iostream>
1. 输入istream
2. 输出ostream
3. iostream继承istream和ostream 所以它具有输入输出功能。
为了方便这个库定义了下列三个标准流对象:
1. cin 代表标准输入istream类对象一般地cin使我们能够从用户终端读入数据。
2. cout 代表标准输出ostream类对象一般地cout使我们能够向用户终端写数据。
3. cerr 代表标准错误ostream类对象一般地cerr是导出程序错误消息的地方。
另外,输出主要由重载的左移操作符<< 来完成类似地输入主要由重载的右移操作符>>来完成。
Demo1:
#include <iostream>
#include <string>
int main() {
string in_string;
// 向用户终端写字符串
std::cout << "Please enter your name: ";
// 把用户输入的读取到 in_string 中
std::cin >> in_string;
if ( in_string.empty() )
// 产生一个错误消息输出到用户终端
std::cerr << "error: input string is empty!\n";
else std::cout << "hello, " << in_string << "!\n";
}
二. 对文件的操作
相关头#include <fstream>
1. ifstream 从istream 派生把一个文件绑到程序上从文件读取数据用来输入。
2. ofstream 从ostream 派生把一个文件绑到程序上用来向文件写入数据。
3. fstream 从iostream 派生把一个文件绑到程序上用来输入和输出。
注:由于在fstream 头文件中也包含了iostream 头文件所以我们不需要同时包含这两个文
件C++对于文件的输入输出也支持同样的输入和输出操作符。
Demo2:
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
int main()
{
string ifile;
cout << "Please enter file to sort: ";
cin >> ifile;
// 构造一个 ifstream 输入文件对象
ifstream infile( ifile.c_str() );
if( ! infile ) {
cerr << "error: unable to open input file: " << ifile << endl;
return -1;
}
string ofile = ifile + ".sort";
// 构造一个 ofstream 输出文件对象
ofstream outfile( ofile.c_str() );
if( !outfile ) {
cerr << "error: unable to open output file: " << ofile << endl;
return -2;
}
string buffer;
vector<string> text;
int cnt = 1;
while ( infile >> buffer ) {
text.push_back( buffer );
cout << buffer << ( cnt++ % 8 ? " " : "\n" );
}
sort( text.begin(), text.end() );
// ok: 把排序后的词打印到 outfile
vector<string>::iterator iter = text.begin();
for ( cnt = 1; iter != text.end(); ++iter, ++cnt )
outfile << *iter << (cnt%8 ? " " : "\n" );
return 0;
}
三. 对字符流操作
相关的头文件#include <sstream>
1 istringstream 从istream 派生从一个字符串中读取数据。
2 ostringstream 从ostream 派生写入到一个字符串中。
3 stringstream 从iostream 派生从字符串中读取或者写入到字符串中。
Demo3:
#include <sstream>
string program_name( "our_program" );
string version( "0.01" );
// ...
string mumble( int *array, int size )
{
if ( ! array ) {
ostringstream out_message;
out_message << "error: "
<< program_name << "--" << version
<< ": " << __FILE__ << ": " << __LINE__
<<" -- ptr is set to 0; "
<< " must address some array.\n";
// 返回底层 string 对象
return out_message.str();
}
四. wchar_t型
支持wchar_t类型的流读写操作
wcin wcout wcerr wiostream 等等....
20.1 输出操作符<<
1. 支持所有的内置数据类型 包括string ,const char* 和complex。以及函数的调用。
2. endl等价于输出换行操作符,然后再刷新缓存区。cout << '\n' << flush;
3. <<可以连接使用,因为operator<<的返回值是ostream&。
4. cout << p << &i << endl;会输出指针的地址(p是int *,i是int)。如果是const char * pcStr不会输出指针地址值,
输出的是字符串。转换为cout << static_cast<void *>(const_cast<char *>(pcStr));
5. <<运算符的优先级高于?:所以和?:使用时注意加括号。
6. ostream_iterator和cout的使用。
Demo4:
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
string pooh_pals[] = {
"Tigger", "Piglet", "Eeyore", "Rabbit"
};
int main()
{
vector<string> ppals( pooh_pals, pooh_pals+4 );
vector<string>::iterator iter = ppals.begin();
vector<string>::iterator iter_end = ppals.end();
cout << "These are Pooh's pals: ";
// 把每个元素拷贝到 cout ...
ostream_iterator< string > output( cout, " " );
copy( iter, iter_end, output );
cout << endl;
}
7. cout << 遇到‘\0’会定制输出。
20.2 输入操作符>>
1. while(cin >> 变量)的时候有两种情况会让循环停止(false):
(1) 读到文件结束在这种情况下我们已经正确地读完文件中所有的值。
(2) 遇到一个无效的值比如3.14159小数点是非法的1e-1字符文字e是非法的或者一般的任意字符串文字在读入一个无效值的
情况下istream对象被放置到一种错误的状态中并且对于值的所有读入动作都将停止。
2. 预定义的输入操作符可以接受任何的内置数据类型包括C 风格字符由以及标准库string和complex 类类型。
3. 缺省情况下输入操作符丢弃任何中间空白空格制表符换行符走纸以及回车。
Demo5:
#include <iostream>
#include <string>
int main()
{
int item_number;
string item_name;
double item_price;
cout << "Please enter the item_number, item_name, and price: " << endl;
cin >> item_number;
cin >> item_name;
cin >> item_price;
cout << "The values entered are: item# "
<< item_number << " "
<< item_name << " @$"
<< item_price << endl;
}
4. 如果希望读入空白符号可以利用cin.get()方法一个个读入,或者设置skipws和noskipws选项(后面介绍)。
5. isream_iterator和cin关联使用。
Demo6:
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
int main()
{
istream_iterator< string > in( cin ), eos ;
vector< string > text ;
// 从标准输入向 text 拷贝值
copy( in , eos , back_inserter( text ) ) ;
sort( text.begin() , text.end() ) ;
// 删除所有重复的值
vector< string >::iterator it ;
it = unique( text.begin() , text.end() ) ;
text.erase( it , text.end() ) ;
// 显示结果 vector
int line_cnt = 1 ;
for ( vector< string >::iterator iter = text.begin();
iter != text.end() ; ++iter , ++line_cnt )
cout << *iter
<< ( line_cnt % 9 ? " " : "\n" ) ;
cout << endl;
}
20.2.1 字符串输入
1. setw函数:防止读入字符串益处(char[4],输入4个以上字节),while ( cin >> setw( bufSize ) >> buf )
这里bufSize是字符数组buf的长度setw()把长度等于或大于bufSize 的字符串分成最大长度为bufSize - 1的两个
或多个字符串,在每个新串的末尾放一个空字符为了使用setw()要求程序包含iomanip头文件#include <iomanip>。
2. 用string没有char *那些长度和内存溢出问题,更容易使用。
20.3 其他输入输出操作符
一. istream的成员get()一次读入一个字节。属于istream
1. get(char& ch)从输入流中提取一个字符包括空白字符并将它存储在ch中它返回被应用的istream对象。
与之对应的是ostream的put()方法,每次读入一个字节,然后返回ostream。
2. get()的第二个版本也从输入流读入一个字符区别是它返回该字符值而不是被应用的istream 对象它的返回类型
是int 而不是char 因为它也返回文件尾的标志end-of-file该标志通常用-1 来表示以便与字符集区分开为测试返回
值是否为文件尾我们将它与iostream 头文件中定义的常量EOF 做比较。
Demo7:
#include <iostream>
int main()
{
int ch;
// 或使用:
// while (( ch = cin.get() ) && ch != EOF)
while (( ch = cin.get()) != EOF)
cout.put(ch);
return 0;
}
3. get(char *sink, streamsize size, char delimiter='\n')
(1) sink 代表一个字符数组用来存放被读取到的字符。
(2) size 代表可以从istream 中读入的字符的最大数目。
(3) delimiter 表示如果遇到它就结束读取字符的动作delimiter 字符本身不会被读入而是留在istream 中作为istream 的
下一个字符一种常见的错误是在执行第二个get()之前忘了去掉delimiter。
二. ignore( streamsize length = 1, int delim = traits::eof )函数: 属于istream
我们用istream 成员函数ignore()来去掉delimiter
缺省情况下换行符被用作delimiter。
三. gcount()函数: 属于istream
它返回由最后的get()或getline()调用实际提取的字符数。
Demo8:
#include <iostream>
int main()
{
const int max_line = 1024;
char line[ max_line ];
while ( cin.get( line, max_line ))
{
// 最大读取数量 max_line - 1, 也可以为 null
int get_count = cin.gcount();
cout << "characters actually read: "
<< get_count << endl;
// 处理每一行
// 如果遇到换行符
// 在读下一行之前去掉它
if ( get_count & max_line-1 )
cin.ignore();
}
}
四. getline(char *sink, streamsize size, char delimiter='\n')属于istream
五. write( const char *sink, streamsize length )属于ostream
提供了另外一种方法可以输出字符数组”它不是输出直到终止空字符为止的所有字符“而是输出某个长度的字符序列包括内含的
空字符它的函数。length 指定要显示的字符个数write()返回当前被调用的ostream 类对象。
六. read( char* addr, streamsize size )属于istream
read()从输入流中提取size 个连续的字节并将其放在地址从addr 开始的内存中gcount()返回由最后一个read()调用提取的字
节数而read()返回当前被调用的istream 类对象。
Demo9:
#include <iostream>
int main()
{
const lineSize = 1024;
int lcnt = 0; // 读入多少行
int max = -1; // 最长行的长度
char inBuf[ lineSize ];
// 读取 1024 个字符或者遇到换行符
while (cin.getline( inBuf, lineSize ))
{
// 实际读入多少字符
int readin = cin.gcount();
// 统计: 行数最长行
++lcnt;
if ( readin > max )
max = readin;
cout << "Line #" << lcnt
<< "\tChars read: " << readin << endl;
cout.write( inBuf, readin).put('\n').put('\n');
}
cout << "Total lines read: " << lcnt << endl;
cout << "Longest line read: " << max << endl;
}
七. getline( istream &is, string str, char delimiter );
这个getline()实例的行为如下读入最大数目为str::max_size-1 个字符如果输入序列超出这个限制则读操作失败
并且istream 对象被设置为错误状态否则当读到delimiter 它被从istream 中丢弃但没有被插入到string 中或遇
到文件结束符时输入结束。
八. putback( char c ); 将字符放回 iostream。
九. unget();往回重置下一个 istream 项。
十. peek(); 返回下一个字符或 EOF,但不要提取出来。
Demo10:
char ch, next, lookahead;
while ( cin.get( ch ))
{
switch (ch) {
case '/':
// 是注释行吗? 用 peek() 看一看:
// 是的? ignore() 余下的行
next = cin.peek();
if ( next == '/' )
cin.ignore( lineSize, '\n' );
break;
case '>':
// 查找 >>=
next = cin.peek();
if ( next == '>' ) {
lookahead = cin.get();
next = cin.peek();
if ( next != '=' )
cin.putback( lookahead );
}
20.4 重载输出操作符<<
输出操作符是一个双目操作符它返回一个ostream 引用重载定义的通用框架如下
// 重载 output 操作符的通用框架
ostream&
operator <<( ostream& os, const ClassType &object )
{
// 准备对象的特定逻辑
// 成员的实际输出
os << // ...
// 返回 ostream 对象
return os;
}
注:因为第一个实参是一个ostream 引用所以输出操作符必须定义为非成员函数,当输出操作符要求访问非公有成员
时必须将它声明为该类的友元。
20.5 重载输入操作符>>
1 由于不正确的格式而导致失败istream 应该把状态标记为fail。setstate( ios_base::failbit )。
2 对于错误状态中的iostream 插入和提取操作没有影响。
Demo11:
#include <iostream>
#include "WordCount.h"
/* 必须修改 WordCount, 指定输入操作符为友元
class WordCount {
friend ostream& operator<<( ostream&, const WordCount& );
friend istream& operator>>( istream&, WordCount& );
*/
istream&
operator>>( istream &is, WordCount &wd )
{
/* WordCount 对象被读入的格式:
* <2> string
* <7,3> <12,36>
*/
int ch;
/* 读入小于符号, 如果不存在
* 则设置 istream 为失败状态并退出
*/
if ((ch = is.get()) != '<' )
{
is.setstate( ios_base::failbit );
return is;
}
// 读入多少个
int occurs;
is >> occurs;
// 取 >; 不检查错误
while ( is && (ch = is.get()) != '>' );
is >> wd._word;
// 读入位置
// 每个位置的格式: < line, col >
for ( int ix = 0; ix < occurs; ++ix )
{
int line, col;
// 提取值
while (is && (ch = is.get())!= '<' );
is >> line;
while (is && (ch = is.get())!= ',' );
is >> col;
while (is && (ch = is.get())!= '>' );
wd.occurList.push_back( Location( line, col ));
}
return is;
}
20.6 文件输入和输出
包含头文件#include <fstream>
1. ofstream的构造函数要制定打开模式
输出模式ios_base::out 或附加模式ios_base::app 等等。在缺省情况下ostream文件以输出模式打开。
注:如果在输出模式下打开已经存在的文件则所有存储在该文件中的数据都将被丢弃如果我们希望增加而不是替换现
有文件中的数据则应该以附加模式打开文件于是新写到文件中的数据将添加到文件尾部在这两种模式下如果文件不存
在程序都会创建一个新文件。
2. 判断是否打开if (!outFile /*ofstream对象*/)
3. 因为ofstream派生于ostream,所以ofstream拥有ostream的操作,比如put()等等。
4. 自定义<<操作输入到ofstream中。
5. 用ifstream读入一个文件,派生于istream,所有拥有istream的操作,比如get()等等。
6. close()函数断开和文件的关联。
7. fstream派生于iostream,它具有对文件的读写操作。
8. seekg和seekp标记当前位置,g是在读文件中使用,p再写文件中使用。
注:第二个参数标示定位标记:
(1) ios_base::beg 文件的开始
(2) ios_base::cur 文件的当前位置
(3) ios_base::end 文件的结尾
9. tellg()或tellp()返回当前位置,g和p意义同seekg和seekp。返回值是ios_base::pos_type。
10. clear()函数:清除状态。
20.7 条件状态
一. 条件状态
1 如果一个流遇到文件结束符则eof()返回true。
2 如果试图做一个无效的操作比如seeking 重定位操作超出了文件尾则bad()返回true,一般地这表示该流由于某种
未定义的方式而被破坏了。
3 如果操作不成功比如打开一个文件流对象失败或遇到一种无效的输入格式则fail()
返回true 例如
ifstream iFile( filename, ios_base::in );
if ( iFile.fail() ) // 不能打开
error_message( ... );
4 如果其他条件都不为true 则good()返回true
二. 改变状态
1. clear()函数,状态变为显示。
2. setstate()函数,添加状态。参数设置为:
ios_base::badbit
ios_base::eofbit
ios_base::failbit
ios_base::goodbit
3. rdstate()获取成员状态,返回值ios_base::iostate。
20.8 string 流
1. 包括头文件#include <sstream>
2. str()返回与ostringstream 类对象相关联的string 对象。
20.9 格式状态
操 作 符 含 义
boolalpha 把true 和false 表示为字符串
*noboolalpha 把true 和false 表示为0 1
showbase 产生前缀指示数值的进制基数
*noshowbase 不产生进制基数前缀
showpoint 总是显示小数点
*noshowpoint 只有当小数部分存在时才显示小数点
Showpos 在非负数值中显示+
*noshowpos 在非负数值中不显示+
*skipws 输入操作符跳过空白字符
noskipws 输入操作符不跳过空白字符
uppercase 在十六进制下显示0X 科学计数法中显示E
*nouppercase 在十六进制下显示0x 科学计数法中显示e
*dec 以十进制显示
hex 以十六进制显示
oct 以八进制显示
left 将填充字符加到数值的右边
right 将填充字符加到数值的左边
Internal 将填充字符加到符号和数值的中间
*fixed 以小数形式显示浮点数
scientific 以科学计数法形式显示浮点数
flush 刷新ostream 缓冲区
ends 插入空字符然后刷新ostream 缓冲区
endl 插入换行符然后刷新ostream 缓冲区
ws 吃掉 空白字符
// 以下这些要求 #include <iomanip>
setfill(ch) 用ch 填充空白字符
setprecision(n) 将浮点精度设置为n
setw(w) 按照w 个字符来读或者写数值
setbase(b) 以进制基数b 输出整数值
注*表示缺省的流状态
20.10 强类型库
iostream库是强类型的例如试图从一个ostream 读数据或者写数据到一个istream都会在编译时刻被捕获到并标记为类型违例。
3.1 什么是内核对象
内核对象就是内核中的一块内存,是一个结构,并且只能由内核对象访问,应用程序只能通过调用Windows提供的函数来操作内核对象。每个内核对象都有相同的部分比如安全属性和使用计数器。
3.1.1 内核对象的使用计数
内核对象中的使用计数和进程无关,当进程第一次创建某个内核对象时候使用计数变为1,当另一个进程也调用此内核对象时计数变为2。当进程释放时或者关闭内核对象时(CloseHandle),内核的使用计数减去1,如果使用计数不为0的话,内核不会释放此内核对象。
3.2.2 安全性
内核对象能够得到安全描述符的保护,安全描述符定义了谁能够创建,访问和使用该对象,一般在服务器代码中使用,客户端可以忽略。
所有创建内核对象的函数的参数都有一个指向SECURITY_ATTRIBUTES结构的指针。
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
只有lpSecurityDescriptor成员和安全属性有关。一般此参数传递NULL,表示默认的安全描述。
如果需要:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = FALSE;
HANDLE h = CreateMutex(&sa, FALSE, "XI");
其余进程可用用OpenMutex函数打开,如果权限可以就返回句柄,如果失败返回NULL,GetLastError被设置为ERROR_ACCESS_DENIED。
Windows除了内核对象之外还有GDI和用户对象,区分它们的简单办法就是,创建函数中带有安全描述符参数的就是内核对象。
3.2 进程的内核对象句柄表
索引 内核对象内存块得指针 访问屏蔽(标志位的DWORD) 标志(标志位的DWORD)
1 0x???????? 0x???????? 0x????????
2 0x???????? 0x???????? 0x????????
… … … …
3.2.1 创建内核对象
调用Create&函数族来创建相应的内核对象,返回的是内核对象的句柄(也有个说法就是句柄表的索引),如果创建失败一般会返回0(NULL),也有的会返回INVALID_HANDLE_VALUE=-1,比如CreateFile失败后会返回后者,失败的原因有可能是内存不足或者是安全问题等等。其他对内核操作的函数都需要此句柄值作为参数传递进去,如果传递一个无效的句柄进去,那么GetLastError函数的值将被置为6(ERROR_INVALID_HANDLE)。
3.2.2 关闭内核对象
BOOL CloseHandle(HANDLE hobj);
调用此函数,系统会了清理进程的句柄表中的对应项目,如果使用计数器为0,内核释放该内核对象的资源,如果使用计数器不为0,说明其他进程还在使用此内核对象,则不释放资源。
当进程忘记调用CloseHandle函数,可能造成内存泄露,但是当进程结束的时候资源一样会被释放。
如果传递的参数无效,则函数返回FALSE,并且GetLastError函数的值被设置成ERROR_INVALID_HANDLE。如果是DEBUG阶段,则返回错误信息。
3.3 跨越进程边界共享内核对象
3.3.1 对象句柄的继承性
- 在父进程创建子进程的时候,将参数SECURITY_ATTRIBUTES结构的Inherithandle字段设置为TRUE的话,再父进程句柄表中标示该内核对象的项的标志位的值将会变成TRUE,标示该内核对象是可以让子进程继承的,具有可继承性(仅仅标示 该句柄值具有可继承性,而内核对象没有可继承性)。
- 将CreateProcess的参数bInherithandle参数的值设置为TRUE,标示创建的进程可以继承有继承性的父进程句柄。
- 子进程创建后不会先加载程序,它先搜索父进程的句柄表将有继承性的项目原封不动的拷贝给自己(索引也没有变,所以句柄值也不变)。
- 父进程可以有3种方式将句柄值传递给子进程,参数传递,进程间通信和环境变量(GetEnvironmentVariavle函数解析)。
BOOL
WINAPI
CreateProcess(
__in_opt LPCSTR lpApplicationName,
__inout_opt LPSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCSTR lpCurrentDirectory,
__in LPSTARTUPINFOA lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
注意:
子进程再创建其子进程,如果满足上面方式,可以继续继承。
如果父进程再创建子进程后,再创建句柄,子进程不会被继承。
3.3.2 改变句柄标志
BOOL SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags);
改变句柄的标志,目前可改变的标志有两种
#define HANDLE_FLAG_INHERIT 0x00000001 // 继承标志
#define HANDLE_FLAG_PROJECT_FROM_CLOSE 0x00000001 // 保护不允许关闭句柄标志
可以用OR操作同时设置2个标志。第一个参数是要设置的句柄值,第二个就是要改变的标志,第三个参数是将标志改编成什么值。
BOOL GetHandleInformation(HANDLE hObkect, PDWORD pdwFlags);
获取当前句柄的标志的值。
// 设置句柄值可继承:
SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
// 设置句柄不可继承:
SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, 0);
// 设置句柄值不可关闭,受保护:
SetHandleInformation(hObject, HANDLE_FLAG_PROJECT_FROM_CLOSE, HANDLE_FLAG_PROJECT_FROM_CLOSE);
// 设置句柄值可关闭,不受保护:
SetHandleInformation(hObject, HANDLE_FLAG_PROJECT_FROM_CLOSE, 0);
3.3.3 命名对象
创建内核对象函数族Create&中的最后一个参数是pszName,该参数是如果传递NULL,表示是匿名内核对象,可以通过其他俩种方式来使用其他进程的内核对象。当pszName参数传递以’\0’(最多长度为MAX_PATH 260字符)结尾的字符串时,表示启用命名对象,比如进程A调用CreateMutex(NULL, FALSE, “XI”)的时候,他将创建内核对象名字为“XI”,之后某一时刻如果进程B也调用CreateMutex(NULL, FALSE, “XI”)函数他将经过以下几步:
- 判断内核对象名称是否相同。
- 判断内核对象类型是否相同,如果名字相同但是类型不相同则Create&函数族返回NULL,GetLastError函数值为6(ERROR_INVALID_HANDLE)。
- 判断安全性,返回同2步,GetLastError值同2步。
- 如果验证通过则返回句柄(返回句柄的值和该内核对象其他句柄的值不一定相同),GetLastError的值等于ERROR_ALREADY_EXISTS。
也可以用Open&函数族来打开已经创建的句柄,成功后GetLastError也不会被设置。具体如下
HANDLE Open&(DWORD, BOOL, PCSTR);
第一个参数:表示访问权限。
第二个参数:表示新创建的句柄是否有继承性(注意不是内核对象!)。
第三个参数:不能传递NULL。如果该句柄不存在则返回NULL,GetLastError被设置为2(ERROR_FILE_NOT_FOUND)。
3.3.4 终端服务器的名字空间
Globad,Local,Session程序保留关键字,具体的没弄明白,理解的就是说当服务器的时候,客户端可以访问以这些名字开头的内核对象。
3.3.5 复制对象句柄
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE TargetProcessHandle,
PHANDLE phTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions);
执行DuplicateHandle函数的进程为ProcessC,原进程为ProcessS,目标进程为ProcessT。则hSourceProcessHandle为进程ProcessS的进程句柄,TargetProcessHandle为进程ProcessT的进程句柄,ProcessC将句柄hSourceHandle从ProcessC拷贝到ProcessT中,值存在phTargetHandle中,dwDesiredAccess新句柄的反问权限,bInheritHandle新句柄的继承性,参数dwOptions有两种类型分别是:
DUPLICATE_SAME_ACCESS忽略参数dwDesiredAccess,新句柄和原进程句柄具有相同的反问权限。
DUPLICATE_CLOSE_SOURCE关闭ProcessS中的拷贝句柄,内核对象的计数不变。
HANDLE hObjProcessS = CreateMutex(NULL, FALSE, NULL);
HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT);
HANDLE hObjProcessT;
DuplicateHandle(GetCurrentProcess(), hObjProcessS, hProcessT , &hObjProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS);
CloseHandle(hObjProcessS);
CloseHandle(hProcessT);
注意:
一般DuplicateHandle函数没有在三个进程中使用,因为很难知道原进程的句柄值。
要使用IPC机制通知目标进程,新句柄已经拷贝过去。