已阅读5页,还剩15页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
读Windows核心编程 - 1 收藏 当调用一个Windows函数失败,可以用DWORD GetLastError(); 返回该线程的32位错误代码。WinError.h头文件中包含了Microsoft公司定义的错误代码列表。每一个错误代码包含消息ID(ERROR_PATH_NOT_FOUND),消息文本和一个号码(0L、1L)三部分。当Windows函数运行失败时,应该立即调用GetLastError函数,如果调用了另一个Windows函数,返回值可能被修改。 从系统内部来说,当一个Windows函数检测到一个错误时,它会使用一个称为线程本地存储器的机制,将相应的错误代码号码与调用的线程关联起来。(线程本地存储器在21章中描述) 在vc6.0中,可以在watch窗口中输入err,hr得到错误代码的英文描述,当Windows函数调用失败时不需要调用GetLastError即可在watch窗口中看见相应的错误描述。还可以利用FormatMessage函数将错误代码转化为文本描述,在创建向用户显示的字符串信息时,它是首选函数。 也可以定义自己的错误代码,用VOID SetLastError(DWORD dwErrCode)实现。错误代码是个32位的数字,可以划分为五个域。这些域在24章中详细讲述。读Windows核心编程 - 2 收藏 Microsoft对Unicode的支持:Windows 98 :只支持ANSIWindows 2000: 即支持Unicode也支持ANSI,但是用Unicode会更快,因为会有一个内部的转化过程,比如调用CreateWindowEx函数,参数传非Unicode字符串,那么CreateWindowEx必须分配内存(进程的默认堆中),将非Unicode字符转化成Unicode字符,然后在传给CreateWindowEx。影响效率。Windows CE:只支持UnicodeCOM:只支持UnicodeC运行时库对Unicode的支持:在标准C头文件String.h中定义:typedef unsigned short wchar_t;对应strcat、strcpy、strlen这样的函数都有一组对应的Unicode版本:wcscat、wcscpy、wcslen对于包含了对str函数和wcs函数进行显示调用的代码来说,无法非常容易的同时为ANSI和Unicode对这些代码进行编译。若要建立双重功能,必须包含头文件TChar.h,而不是String.h。TChar.h中定义了很多宏,比如_tcscpy,如果定义了_UNICODE这个宏就调用wcscpy,如果没有定义就调用strcpy函数。另外TChar.h中还定义了:定义了_UNICODE:typedef wchar_t TCHAR;define _TEXT(x) L#x 没有定义 _UNICODE:typedef char TCHAR;define _TEXT(x) x这样,无论有没有定义_UNICODE,都可以这么用:TCHAR *szError = _TEXT(Error);通过这种方法,只需通过_UNICODE就可以分别编译出ANSI和Unicode版本。另外: _T() 等同于 _TEXT()。Windows头文件定义的数据类型:WCHAR :Unicode字符串PWSTR:指向Unicode字符串的指针PCWSTR:指向一个恒定的Unicode字符串的指针Windows也定义了ANSI/Unicode通过数据类型PTSTR和PCTSTR,这些数据类型可以指ANSI字符串,也可以指Unicode字符串,取决于是否定义了UNICODE宏。(这里没有下划线,跟C运行时库不同)Windows提供的函数通常包含两个版本:比如CreateWindowExA和CreateWindowExW,还有一个宏CreateWindowEx,调用哪里取决于是否定义了UNICODE。而在Windows 2000下,因为没有ANSI版本,CreateWindowExA其实做了从ANSI版本到Unicode版本的转化工作。要创建给其他开发人员使用的DLL时可以考虑提供两个版本的输出函数:ANSI版本和Unicode版本。在ANSI版本中就进行简单的字符串转化,在调用Unicode版本。Windows API中一些函数仅仅为了实现与16位Windows的兼容,像WinExec和OpenFile,应该用CreateProcess和CreateFile代替。Windows还提供了一组操作字符串的函数:StrCat、StrChr、StrCmp、StrCpy.这些函数同样支持ANSI和UNICODE版本,取决于是否定义了UNICODE宏,书上建议使用这些函数代替C运行期字符串函数,因为这些函数被很多程序使用,比如Explorer.exe,在我们的应用程序运行时,它们可能已经被装入RAM。但是可惜没有见到过程序中使用这类函数的,所以也不知道这个说法是否正确。如果要使用这些函数,要包含头文件ShlWApi.h。要成为符合ANSI和Unicode的应用程序,应该符合以下几条规则:1. 将文本串视为字符数组,而不是char数组和字节数组2. 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串3. 将显示数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存4. 将TEXT宏用于原义字符和字符串5. 执行全局性替换(用PTSTR替换PSTR)6. 修改字符串运算操作(sizeof(szBuffer/sizeof(TCHAR)、maloc(nCharacters*sizeof(TCHAR)在Windows中,还提供了一组字符串操作的函数(宏):lstrcat、lstrcmp、lstrcmpi、lstrcpy、lstrlen.他们也根据UNICODE宏选择调用ANSI版本还是Unicode版本的函数。有两个函数于C运行时函数是不同的,即lstrcmp和lstrcmpi,C运行期函数strcmp、strcmpi、wcscmp、wcscmpi只是对字符串的代码点的值进行比较,这就是说,这些函数将忽略实际字符的意义。而Windows版本的函数是通过调用CompareString函数实现的。这个函数的第一个参数是LCID lcid,CompareString使用这个ID来比较这两个字符串,方法是对照一种特定的语言来查看他们的字符的含义。这个ID是通过调用LCID GetThreadLocale()函数得到的。其他C运行时函数没有为Unicode提供很好的支持。例如,tolower和toupper函数无法正确的转换带有重音符号的字符。为了弥补C运行期库的不足,可以调用下面的Windows函数:PTSTR CharLower(PTSTR pszString)和PTSTR CharUpper(pszString),这些函数也可用于转换ANSI字符串。另外Windows还提供了IsCharAlpha(THCR ch)、IsCharLower(TCHAR ch)等函数,与C运行期函数不同的是,这些函数还要考虑用户在控制面板中指定的语言。另外Microsoft公司还提供了wsprintf等函数使我们能很容易地对ANSI和Unicode进行混合和匹配。当资源编译器对你的所有资源进行编译时,输出文件是二进制文件。资源(字符串表、对话框模板等)中的字符信息总是Unicode字符串。如果应该程序没有定义UNICODE,那么系统会进行内部转化。比如在编译源代码模块时没有定义UNICODE,调用LoadString实际上调用了LoadStringA。这时LoadStringA就从你的资源中读取字符串,并将该字符转换成ANSI字符串。ANSI形式的字符串将从该函数返回给你的应用程序。MultiByteToWideChar和WideCharToMultiByte用于多字节字符串和宽字符串之间的转换。用法比较复杂。可参看核心编程page 24本文来自CSDN博客,转载请标明出处:/panda1987/archive/2008/04/01/2241981.aspx66读Windows核心编程 - 3 收藏 什么是内核对象?比如:事件对象,文件对象,文件映射对象,互斥对象,进程对象,作业对象,线程对象等等都是内核对象。每个内核对象只是内核分配的一个内存块,并且只能 由内核访问。该内存块是一个数据结构,它的成员负责维护该对象的各种信息。当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,进程中的任何线程都可以使用这个值。注意:该句柄是与进程相关的,也就是说,如果将该句柄值传给另外一个进程,那么这个进程使用这个句柄值就会失败。当然,通过一些特殊的机制可以使多个进程能够共享单个内核对象。稍后会介绍。 内核对象是有内核拥有的,而不是进程。内核对象的存在时间可以比创建该对象的进程长。每个内核对象都包含一个使用计数,用于记录有多少进程正在使用它,当一个新的进程访问它的时候,该计数就增1,反之减1。当减到0的时候,也就是没有进程引用它的时候该内核对象被撤销。 内核对象能够得到安全描述符的保护。安全描述符用于描述了谁创建了这个对象,谁能够访问这个对象,谁无权访问等等。安全描述符通常在编写服务器应用程序的时候使用,客户端程序可以忽略这个特性。用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针作为参数。大多数应该程序只是为该参数传递NULL,这样就可以创建带有默认安全性的内核对象,默认安全性意味着对象的管理小组和对象的创建者都拥有全部的访问权。当你需要获得对相应的一个内核对象的访问权时,必须设定要对该对象执行的操作。比如:OpenFileMapping(FILE_MAP_READ, FALSE, MyFileMapping); 它在返回一个有效的句柄值之前,先要执行一次安全性检查,如果(已登录用户)被允许访问该对象,则返回一个有效的句柄值。否则,返回NULL。 除了内核对象外,你的应用程序也可以使用其他类型的对象,如菜单、窗口、图标、字体等。而这些对象属于用户对象或图形设备接口(GDI)对象。要判断一个对象究竟是用户对象还是内核对象,最简单的方法就是看它的参数是否有一个可以用来设置安全性属性的参数。 当一个进程初始化时,系统要为它分配一个句柄表。该表只用于内核对象,不用于用户对象和GDI对象。表中存放了内核对象的索引、内核对象的地址、以及一些标志。用于创建内核对象的所有函数均返回与进程相关的句柄,这些句柄可以被相同进程中的任何线程使用。该句柄值实现上是放入进程的句柄表中的索引。但是要记住,句柄的含义没有记录文档资料,并且随时可能变更。如果创建内核对象失败,在判断时需要注意,因为有些函数会返回NULL,而有些会返回INVALD_HANDLE_VALUE。无论怎么创建内核对象,都需要调用CloseHandle来结束对该对象的操作。该函数会检查调用进程的句柄表,如果传进来的句柄是有效的,那么就可以通过索引找到该内核对象的地址,并确定该结构中引用计数的值。如果是0,该对象便从内核中撤销。如果忘记调用CloseHandle,就有可能出现内存泄漏。但是当进程终止运行时,系统能够保证该进程使用的任何资源被释放,当然也包括内核对象。内核对象在许多情况下需要被共享,如:1. 文件映射对象可以使你能够在同一台机器上运行的两个进程之间共享数据2. 邮箱和指定的消息管道使得应用程序能够在连网的不同机器上运行的进程之间发送数据块3. 互斥对象、信标和事件使得不同的进程中的线程能够同步它们的连续运行跨越进程边界共享内核对象:1. 对象句柄的可继承性 只有当进程具有父子关系时,才能使用对象句柄的继承性。要实现继承,首先在父进程创建内核对象的时候要指明它希望对象的句柄是个可继承的句柄。这个工作需要在SECURITY_ATTRIBUTES结构中指定,为成员bInheritHandle设置为TRUE。进程的句柄表中的标志位指定了该对象的可继承性。0表示不可继承,1表示可继承。除此之外,当父进程调用CreateProcess创建子进程的时候也需要将SECURITY_ATTRIBUTES结构中的bInheritHandle设置为TRUE,告诉系统希望子进程继承父进程的句柄表中的可继承句柄。除了拷贝句柄表项目外,系统还要递增内核对象的使用计数等等。父进程和子进程在该内核对象的关系跟普通的进程一样。此时,子进程只要获得对象的索引值就可以访问到该对象。句柄值的传递方法有很多,最常用的方法是将句柄作为一个命令行参数传递给子进程。另外也可以通过进程间通信的方式,方法之一是让父进程等待子进程完成初始化,然后父进程可以将一条消息发送或展示在子进程中的一个线程创建的窗口中。方法之二是让父进程将一个环境变量添加给他的环境程序块。该变量的名字是子进程知道要查找的某种信息,而变量的值则是内核对象要继承的值。 改变句柄的标志:有时父进程可能需要创建两个子进程,然而只想其中的一个继承内核对象的句柄。可以通过SeHandleInformation函数可以改变内核对象的继承标志。也可以通过GetHandleInformation获得内核对象的标志。2. 命名对象 共享跨越进程边界的内核对象的第二种方法是给对象命名。许多(不是全部)内核对象都可以是命名的,在创建这些对象的时候可以指定对象的名字。如果给这个参数传递NULL就创建无名对象。但是Microsoft没有提供为内核对象赋予名字的原则。例如:当我们试图创建一个称为JeffObj的对象的时候,不能保证系统中不存在一个名字为JeffObj的对象。因此,对象下面这个CreateSemaphore的调用总是失败: HANDLE hMutex = CreateMutex(NULL, FALSE, JeffObj); HANDLE hSem = CreateSemaphore(NULL, 1, 1 , JeffObj);那么,如何使用命名对象来共享对象。首先,在进程A中我们调用CreateMutex(NULL, FALSE, JeffObj)函数,然后在进程B中也调用相同的函数,此时系统首先要查看是否已经存在一个名为JeffObj的内核对象,如果确定存在,内核就会检查对象的类型,在这个例子中,由于两次JeffObj对象都是互斥对象,因此系统会执行一次安全性检查,以确定调用者是否拥有对该对象的完整访问权,如果有这种访问权,系统就在B进程的句柄表中找出一个空项,指向这个内核对象。这里有一个地方需要注意,就是在第二次调用CreateMutex函数时,它的前两个参数将被忽略。另外还可以在调用进程B中调用OpenMutex来实现对象的共享,于Create*函数的唯一的区别是,如果对象不存在,那么Open*函数就运行失败。下面的例子可以确保已有一份程序的实例在运行:int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow). HANDLEh = CreateMutex(NULL, FALSE, FA531CC1 - 0497 - 11d3 - A180 - 00105A276C3E); if(GetLastError() = ERROR_ALREADY_EXISTS) return 0; /. CloseHandle(h); return 0;3. 复制对象句柄 共享跨越进程边界的内核对象的最后一个方法是使用DuplicateHandle函数。该函数读取一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。DuplicateHandle可以涉及3个不同进程,比如,我们可以在进程C中调用这个函数,使得进程S句柄表中的一个项目拷贝到进程T中,得到的内核对象的句柄可以通过窗口消息或某种别的IPC机制传给进程T。当然,更常见的是涉及两个进程的情况,代码如下:/ All of the following code is executed by process SHANDLE hOjbProcessS = 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(hProcessT);CloseHandle(hObjProcessS);但是上述代码中不能调用CloseHandle(hObjProcessT),结果可能失败,也可能不会失败。可能会错误的关闭某个其他对象,也可能导致错误的访问。这个函数还有另一种使用方法,假设一个进程拥有一个文件映射对象的读写访问权。在某个位置上,一个函数被调用,它仅仅读取这个文件映射对象。为了避免错误的写入,我们可以通过DuplicateHandle在本进程中在复制一个文件映射对象,但是在复制的时候改变其访问权限,使其变成只读。通过这样的方法可以增加代码的健壮性。本文来自CSDN博客,转载请标明出处:/panda1987/archive/2008/04/04/2251361.aspx读Windows核心编程 - 4 收藏 进程分为两部分,一个是操作系统用来管理进程的内核对象,一个是地址空间。进程是不活泼的,活泼的是线程,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。 Windows支持两种应用程序,一种是GUI(基于图形用户界面),还有一种CUI(基于控制台用户界面)。Windows应用程序必须有一个在应用程序启动运行时的进入点函数。一共有4个:WinWain、wWinMain、main、wmain。操作系统实际上不直接调用我们编写的进入点函数。它调用的是C/C+运行期启动函数,其对应的函数也有四个:WinMainCRTStartup、wWinMainCRTStartup、mainCRTStartup、wmainCRTStartup。当我们用Visual C+创建一个应该程序项目时,Visual C+会根据创建的程序的类型指定链接程序开关,如果是GUI,链接程序的开关是/SUBSYSTEM:WINDOWS,如果是CUI,链接程序的开关是/SUBSYSTEM:CONSOLE。一个新手常见的错误是,创建了一个win32的应用程序,但是创建了一个进入点函数mian,这样因为在链接程序中的开关已经设定为/SUBSYSTEM:WINDOW,会产生一个链接错误。最好的办法是把链接程序开关删除,这样程序就可以自动的确定应该程序应该链接到哪个子系统。C/C+运行期启动函数功能归纳如下:1. 检索指向新进程的完整命令行的指针2. 检索指向新进程的环境变量指针3. 对C/C+运行期的全局变量进行初始化,比如:_pgmptr - 正在运行的程序的全路径和名字4. 对所有全局和静态C+对象调用构造函数当进入点返回后,启动函数便调用C运行时的exit函数。Exit函数负责下面的操作:1. 调用由_onexit函数的调用而注册的任何函数2. 为所有的全局和静态C+对象调用析构函数3. 调用系统ExitProcess函数 在我们调用LoadIcon这样的函数时,通常需要指定一个HINSTANCE(HMODULE)参数来指明哪个文件(可执行文件还是DLL文件)包含你想加载的资源。这个参数就是WinMain中的hinstExe参数,该参数实际值是系统将可执行文件加载到进程地址空间时使用的基本地址空间,而这个基地址是由链接程序决定的。调用GetModuleHandle函数可以返回可执行文件或DLL文件加载到进程的地址空间时所用的句柄/基地址,参数传一个以0结尾的字符串。如果传递NULL就得到可执行文件的基地址,这正是C运行期函数调用WinMain时执行的操作。这个函数注意两点:1. 它只可以获得本进程的地址空间。2. 如果传递NULL,在DLL中调用也返回可执行文件的基地址。 每个进程都有一个与它相关的环境块。环境块是进程地址空间中分配的一个内存块。形式像这个样子:VarName=VarVaule,注意:等于号两边的空格都被考虑在内。如果通过注册表修改了环境变量,需要再次登录才能有效。如果想通过注册表修改,并让有关应用程序更新他们的环境块,可以调用如下代码:SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) TEXT(Environment);子进程可以继承一组与父进程相同的环境变量。但是,父进程能够控制子进程继承什么样的环境变量。这里指的继承是复制,也就是子进程之后对环境变量的修改影响不到父进程。通过GetEnvironmentVariable函数可以确定某个环境变量是否存在以及它的值。SetEnvironmentVariable函数则用于添加、删除、修改环境变量。 通过SetErrorMode函数可以设置进行的错误模式,该模式是指当进程遇到严重错误时应该如何作出反映,这些错误包括磁盘介质故障,未处理的异常,文件查找失败等。通常子进程继承父进程的错误模式标志,也可以在CreateProcess函数中传递CREATE_DEFAULT_ERROR_MODE防止子进程继承。 如果用CreateFile来打开一个文件(不设定全路径),那么系统就会在当前驱动器当前目录中查找该文件。系统总是在内部保持对进程的当前驱动器和目录的跟踪。这些信息是由进程维护的。通过GetCurrentDirectory和SetCurrentDiretory可以获得和设置进程的当前驱动器和目录。有些操作系统支持多个驱动器的当前目录的处理,例如,进程拥有下面所示的两个环境变量: =C:=C:UtilityBin =D:=D:Program Files, 现在的当前目录是C:UtilityBin,并且你调用CreateFile来打开D:ReadMe.txt,那么系统查看环境变量=D。因为=D存在,因此系统试图从D:Program File目录打开该文件。如果不存在,系统就试图从驱动器D的根目录打开该文件。子进程的环境块不会自己继承父进程的当前目录。如果想要子进程继承父进程的当前目录,该父进程必须创建这些驱动器名的环境变量。调用GetFullPathName,父进程可以获得它的当前目录。如:GetFullPathName(TEXT(C:), MAX_PATH, szCurDir, NULL); Windows提供了三个函数可以确定操作系统版本相关的函数:GetVersion,GetVersionEx,VerifyVersionInfo.具体使用方法参看核心编程page56.CreateProcess:原型如下:BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation); 1. lpApplicationName和lpCommandLine 这两个参数可执行文件和命令行字符串。注意到一点,lpCommandLine的原型是LPTSTR,这意味着CreateProcess希望你传递一个非常量的字符串地址。如果传进去一个TEXT(“NOTEPAD”)就会出现违规访问的问题。但是如果传进去一个ANSI字符串,就不会出现这个问题,因为系统已经制作了一个命令行字符串的临时拷贝。如果lpApplication不是NULL(大多数情况是NULL),那么可以将包含想运行的可执行文件的字符串地址传递给lpApplicationName参数。这里必须提供文件的扩展名,系统不会自动假设有一个exe扩展名。如果没用提供全路径,那么系统只在当前目录查找,如果找不到则运行失败。如果lpApplicationName是NULL,那么需要为lpCommandLine提供一个完整的命令行,系统假定字符串中的第一个标记是可执行文件的名字,如果没有指定扩展名,系统假定扩展名为exe。如果在lpCommandLine中没有指定全路径,那么系统依次在以下目录中查找:包含调用进程的.exe文件的目录-调用进程的当前目录-Windows系统目录-Windows目录-PATH环境变量中列出的目录。2. lpProcessAttributes、lpThreadAttributes和bInheritHandles 可以使用lpProcessAttributes和lpThreadAttributes参数分别设定进程对象和线程对象的安全性。如果传递NULL则赋予默认安全性描述符。否则需要传递一个SECURITY_ATTRIBUTES的数组来创建自己的安全性权限。调用CreateProcess将在父进程的句柄表中新增两项,进程对象和线程对象,而SECURITY_ATTRIBUTES结构体中的bInheritHandles成员则确定了该对象的可继承性。比如:Process A中调用了CreateProcess创建进程B,并设定lpProcessAttributes.bInheritHandles = TRUE, lpThreadAttributes.bInheritHandles = FALSE。现在A又调用CreateProcess创建进程C,如果bInheritHandles设置为FALSE,那么两个内核对象都得不到继承,如果bInheritHandles设置为TRUE,那么进程B对象得到继承,也就是复制到了进程C的句柄表中。而线程B对象得不到继承。与此同时,进程A的句柄表中有多了进程C和线程C两项。3. dwCreationFlags 这个参数用于规定如何创建进程,可以用OR操作符将多个标志组合起来。比如,CREATE_SUSPENDED表示新创建的主线程挂起。类似的参数有很多,可以参考核心编程page63。另外dwCreationFlags参数还可以设定进程的优先级类。4. lpEnviroment lpEnviroment参数用于指向包含新进程要使用的环境字符串的内存块。如果传递NULL,子进程将继承它的父进程正在使用的一组环境字符串。将GetEnvironmentStrings()的返回值传递给这个参数效果跟传递NULL一样,该函数获得调用进程正在使用的环境变量字符串数据块的地址。当不再需要该内存块时,应该调用FreeEnvironmentStrings函数将内存块释放。5. lpCurrentDirectory 这个参数允许父进程设置子进程的当前驱动器和目录。如果本参数为NULL,则新进程的工作目录将与生成新进程的应用程序的目录相同。如果不传递NULL,那么必须指向包含需要的工作驱动器和工作目录的以0结尾的字符串。6. lpStartupInfo 这是一个结构体,里面有很多成员,一般情况下只需要把该结构体全部赋为0,把该结构体的大小赋给其中的一个成员就可以了。这个结构体中有些成员只有在子应用程序创建一个重叠窗口时才有意义,而另一些成员则只有在子应用程序执行基于CUI的输入输出时才有意义。具体每个成员的意义可参看核心编程page65。最后,应用程序可以调用GetStartupInfo以便获得由父进程初始化的STARTUPINFO结构的拷贝。子进程可以查看该结构,并根据该结构的成员的值来改变它的行为特征。虽然在windows文档中没有明确的说明,但是在调用这个函数之前必须像下面这样对该结构的cb成员进程初始化:STARTUPINFO si = sizeof(si); GetStartupInfo(&si);7. lpProcessInformation 结构体PROCESS_INFORMATION一共包含四个成员,进程和线程的句柄以及ID。在创建进程的时候,系统为每个对象赋予一个初始引用计数1。然后,在CreateProcess返回之前,该函数打开进程对象和线程对象,并将每个对象的与进程相关的句柄放入PROCESS_INFORMATION结构中。当CreateProcess在内部打开这些对象时,每个对象的引用计数变为2。这意味着如果要使系统释放进程对象,除了该进程必须终于外,父进程必须调用CloseHandle。线程也同样。进程ID和线程ID都是独一无二的标志符,其他内核对象不能使用相同的ID,另外进程和线程也不能使用相同的ID。如果应用程序使用ID来跟踪线程或者进程,必须注意一点,就是这些ID可以重复使用,意思是说,如果一个ID为122的进程对象被释放,那么当另外一个进程创建起来时,122就可以被赋给这个进程。所以最好不要用ID,应该定义一个持久性更好的机制,比如内核对象和窗口句柄等。终止进程的运行:若要终止进程的运行,可以使用下面四种方法:1. 主线程的进入点函数返回(最好的方法) 这种方法是保证所有线程资源能够得到正确清楚的唯一办法: 1. 该线程创建的任何C+对象将能使用他们的析构函数正确的撤销。 2. 操作系统能将正确地释放该线程的堆栈使用的内存。 3. 系统将进程的退出代码(在进程的内核对象中维护)设置为进入点函数的返回值。 4. 系统将进程的内核对象的引用计数递减1.2. 进程中的一个线程调用ExitProcess(应该避免使用) 正常情况下,当主线程的进入点函数返回时,它将返回给C/C+运行期启动代码,它能正确地清楚该清楚使用的所有的C运行时资源。当C运行期资源被释放以后,C运行期启动代码就显示调用ExitProcess,并将进入点函数的值传递给它。注意,调用ExitProcess或ExitThread可使进程或线程在函数中就终止运行。就操作提供而言,就很好,进程或线程的所有操作系统资源都将被全部清楚。但是,C/C+应用程序应该避免使用这些函数,因为C/C+运行期也许无法正确地清楚。这里有个问题不是很明白,操作系统不负责C/C+资源的清理?进程结束了,所有的内存都被收回,还有什么资源得不到释放的么?3. 另一个进程中的线程调用TerminateProcess(应该避免使用) 与ExitProcess不同的是,这个函数可以终止另一个进程或它自己进程的运行。虽然进程确定没有机会执行自己的清楚操作,但是操作系统可以在进程之后进行全面的清除,使得所有操作系统资源都不会保留下来。这意味着进程使用的所有的内存都被释放,所有打开的文件全部关闭,所有内核对象的引用计数均被递减,同时所有的用户对象和GDI对象均被撤销。注意:TerminateProcess是个异步函数,因此无法保证进程被终止。如果想要确切了解进程是否已经终止运行,必须调用WaitForSingleObject或者类似的函数,并传递进程的句柄。4. 进程中的所有线程自行终止运行(几乎不会发生)当进程终止时出现的情况:1. 进程中的所有线程被终止2. 进程指定的用户对象和GDI对象均被释放。所有内核对象均被关闭(递减.)。3. 进程的退出代码将从STILL_ACTIVE改为传递给ExitProcess或TerminateProcess的代码。4. 进程内核对象的状态变为收到通知状态(详见第9章)。5. 进程内核对象的引用计数减1,如果降为0,内核对象被撤销。 进程内核对象的生命期比进程长,进程内核对象维护关于进程的统计信息。即使进程已经终止运行,该信息也是有用的。例如,你可能想要知道进程需要多少CPU时间,或者,你想通过调用GetExitCodeProcess来获得目前已经撤销的进程的退出代码。如果调用GetExitCodeProcess的进程还未终止,得到的退出代码为STILL_ACTIVE,否则便返回进程的退出代码值。子进程:PROCESS_INFORMATION pi;DWORD dwExitCode;BOOL fSuccess = CreateProcess(., &pi);if(fSuccess). CloseHandle(pi.hThread); WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &dwExitCode); CloseHandle(pi.hProcess);CloseHandle(pi.hThread):当CreateProcess返回后立即关闭了子进程的主线程的内核对象。原因是如果子进程的主线程又产生了一个线程而自己终止运行,那么系统就可以从内存中释放子进程的主线程对象。WaitForSingleObject(pi.hProcess, INFINITE):父进程挂起,直到子进程终止运行。当WaitForSingleObject返回时,就可以调用GetExitCodeProcess获得子进程的退出代码。 如果要创建一个独立运行的子进程,在CreateProcess之后直接关掉进程和线程的内核对象就可。这就是Explorer的运行方式。当Explorer为用户创建一个新进程后,它并不关心该进程是否继续运行,也不在乎用户是否终止它的运行。读Windows核心编程 - 5 Windows2000 提供了一个新的作业内核对象,使你能够将进程组合在一起,并且创建一个沙框,以便限制进程能够进程的操作,最好将作业对象视为一个进程的容器。与其他所有内核对象相似,创建作业对象需要调用Handle CreateJobObject(PSECURITY_ATTRIBUTES psa, PCTSTR pszName),第一个参数与其他类似Create*函数相同,第二个参数用于给作业对象命名,使它可以供另一个进程通过OpenJobObject访问。关闭作业对象同样使用CloseHandle函数,应该知道,关闭作业对象并不会使作业中的所有进程终止运行。该作业对象实际上做了删除标记,只有当所有进程全部终止后才撤销。关闭作业对象后,尽管对象没有立即撤销,但是该作业无法被进程访问。通常可以给一个作业加上不同类型的限制:1. 基本限制和扩展限制,用于防止作业中的进程垄断系统的资源。2. 基本的UI限制,用于防止作业中的进程改变用户界面。3. 安全性限制,用于防止作业中的进程访问保密资源(文件,注册表子关键字)通过下面的代码,可以给作业加上各种限制:BOOL SetInformationObject( Handle hJob, /标识要限制的作业 JOBOBJECTINFORMATION JobObjectInformationClass, /指明要限制的类型,共四种 PVOID pJobObjectInformation, /限制设置值的数据结构的地址 DWORD cdJobObjectInformationLength /该结构的大小); 限制的类型一共四种,对应的PVOID指向的结构体类型分别如下:基本限制-JobObjectBasicLimitInformation-JOBOBJECT_BASIC_LIMIT_INFORMATION扩展基本限制-JobObjectExtendedLimitInformation-JOBOBJECT_EXTENDED_LIMIT_INFORMATION基本UI限制-JobObjectBasicUIRestrictions-JOBOBJECT_BASIC_UI_RESTRICTIONS安全性限制-JobObjectSecurityLimitInformation-JOBOBJECT_SECURITY_LIMIT_INFORMATION具体每一种提供了哪些限制这里不再详述,可参看核心编程page94。 对作业实施一些限制后,我们通过CreateProcess创建新的进程,并可以调用AssignProcessToJobObject将该进程指到为某一作业。这里调用CreateProcess时要用CREATE_SUSPENDED标志,因为如果允许子进程立即执行起来,那么它将跑出我们的沙框。因此在我们允许它开始运行之前,我们必须调用AssignProcessToJobObject函数将进程放入我们新建的作业中。调用完AssignProcessToJobObject之后,我们调用ResumeThread函数使新进程在作业的限制下开始运行。一旦进程成为一个作业的一部分,它就不能转到另一个作业。另外,当属于作业中的进程创建了另一个进程后,该进程将自动成为父作业的组成部分。有两种办法可以使新产生的进程不属于该作业:1. 打开JOBOBJECT_BASIC_LIMIT_INFORMATION的LimitFlags成员中的JOB_OBJECT_BREAKAWAY_OK,告诉系统新产生的进程可以在作业外运行。并且调用CreateProcess时用CREATE_BREAKAWAY_FROM_JOB标志。2. 打开JOBOBJECT_BASIC_LIMIT_INFORMATION的LimitFlags成员中的JOB_OBJECT_SILENT_BREAKAWAY_OK标志。这样不需要在CreateProcess中传递任何其他标志。 若要撤销作业中的进程,可用TerminateJobObject,这类似为作业中的每个进程调用TerminateProcess。 QueryInformationJobObject函数可以用来获取对作业的当前限制,用法与SetInformationObject类似,需要传递限制的类型给第二个参数。这个函数也可以用来获得关于作业的统计信息。方法是为第二个参数传递JobObjectBasicAccountingInformation,并传递JOBOBJECT_BASIC_ACCOUNTING_INFORMATION结构的地址。除此之外,还可以获得I/O统计信息、获得作业中运行的进程的一组进程ID等。可以使用Performance Data Helper函数库(PDH.dll)中的函数来检索作业信息,也可以使用Microsoft Management Console(MMC)来参看作业信息。但是MMC只能为已经命名的作业对象获取性能计数器信息,所以我们调用CreateJobObject时应该创建带有名字的作业对象。作业通知信息: 有时候我们需要知道作业中何时生成新进程或者作业中的进程何时终止等消息,比如我们关心的是分配所有CPU的时间是否已经到期,那么可以非常容易地得到这个通知消息。当作业中的进程尚未用完分配的CPU时间时,作业对象就得不到通知。一旦分配的所有C
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 极端降水下医院排水系统与功能恢复关联
- 临沂四年级数学几何作图专项训练卷
- 极端天气下医疗资源短缺的舆情化解策略
- 贵州省毕节市黔西市2025-2026学年高一下学期5月期中物理试卷(答案不全)
- 第一节 微生物的分离和纯培养说课稿2025学年高中生物中图版选修一生物技术实践-中图版
- 26年转运体研究脉络梳理
- 2025-2026学年江苏省盐城市阜宁中学高一(下)期中数学试卷(含答案)
- 肺癌术后呼吸机使用与撤离
- Lesson 13:Be Careful,Danny!说课稿2025学年初中英语冀教版2012九年级全册-冀教版2012
- 高中2025消防员心理韧性说课稿
- 2026广东肇庆市四会市龙甫镇专职消防队人员招聘1人笔试备考题库及答案解析
- (四模)新疆2026年高三普通高考五月适应性文科综合试卷(含答案及解析)
- 2026年中国工商银行校园招聘考试笔试试题及答案解析
- 2026年中考政治百校联考冲刺押题密卷及答案(共九套)
- 哈尔滨市达标名校2026届中考语文模拟预测题含解析
- 国资委安全生产十条硬措施
- 2026防灾减灾日安全培训课件
- 2026年 成都 事业单位考试 真题
- 2026年2026届高三第二次模拟考试化学试题+答案新版
- (二模)2026年广州市普通高中高三毕业班综合测试(二)物理试卷(含答案及解析)
- 浙江省湖州、衢州、丽水三地市2026届高三下学期4月二模技术试题(含答案)
评论
0/150
提交评论