第1112讲windows进程基础_第1页
第1112讲windows进程基础_第2页
第1112讲windows进程基础_第3页
第1112讲windows进程基础_第4页
第1112讲windows进程基础_第5页
已阅读5页,还剩43页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

中科研CASoft软件工程师培训讲义Game

Master游戏修改工具第11~12讲Windows进程基础中科天地软件人才培训中心Created

by本讲重点提要¤

本讲首先介绍了Windows基于GUI的应用程序的入口,并通过范例描述了GUI和CUI应用的区别和练习,之后对

Windows进程的概念做了较为详细的讲述,包括进程的实例句柄、环境变量、当前目录等概念,并通过范例对进程的各种属性进行比较深入的了解。最后通过ExeBrowser范例练习了CreateProcess函数的使用。编写Windows应用程序¤Windows支持两种类型的应用程序。一种是基于图形用户界面(GUI)的应用程序,另一种是基于控制台用户界面(CUI)的应用程序。编写所有基于GUI的Win32应用程序时,都必须在源代码中实现一个WinMain函数。该函数必须使用如下原型:int

WINAPI

WinMain(HINSTANCE

hinstExe,

HINSTANCEhinstExePrev,

LPSTR

lpszCmdLine,

int

nCmdShow);¤该函数实际上不是由操作系统调用的,操作系统调用的是C/C++的运行时的启动函数,VisualC++的链接器知道该函数的名字:WinMainCRTStartup,查看VisualC++中的CRT0.C文件,可以看该函数的代码,我们把它摘录如下:WinMainCRTStartup#ifdef

_WINMAIN_#ifdef

WPRFLAGvoidwWinMainCRTStartup(#else

/*

WPRFLAG*/void

WinMainCRTStartup(#endif

/*

WPRFLAG

*/#else

/*

_WINMAIN_*/#ifdef

WPRFLAGvoidwmainCRTStartup(#else

/*

WPRFLAG*/voidmainCRTStartup(#endif

/*

WPRFLAG

*/#endif

/*

_WINMAIN_

*///支持Unicode

版本的wWinMain//支持ANSI

版本的WinMain//支持Unicode版本的wmain//支持ANSI

版本的mainWinMainCRTStartup¤

从上面我们可以看到该函数支持的几种

Win32程序的入口函数,该函数负责执行以下任务:得到一个新进程的命令行的指针。得到一个新进程的环境变量指针。通过包含STDLIB.H来初始化能被应用程序访问的C运行时全局变量。初始化由C运行时(Run

Time)内存分配函数

malloc/calloc使用的堆和其它低级输入/输出例程。调用WinMain函数(对于WinMain函数而言)。当WinMain返回后,调用C运行时的exit函数,执行清理工作,然后调用Win32的ExitProcess函数。编写Windows应用程序¤

由上面的讨论,我们还可以获得这样的信息,即:malloc/calloc函数分配的内存属于Win32进程的“堆(Heap)”。以后我们将知道Win32进程/线程中堆和栈的概念。¤

本讲下面的部分,我们将解释这里提到的命令行、环境变量等概念。范例1:在GUI程序中使用Console¤

范例:在GUI应用程序中使用Console。事实上Win32中基于GUI的应用和基于CUI的应用直接的界限是很模糊的,我们可以在一个CUI的应用程序中使用对话框(比如最简单的弹出一个

MessageBox),也可以在GUI的应用中向一个控制台窗口输出字符。范例GuiConsoleApp显示了在GUI应用中使用Consoled的简单方法。虽然该应用程序中没有使用对话框,但该程序入口函数为WinMain,由此我们知道它是一个基于GUI的应用程序。¤练习:仿照范例,编写一个MFC单文档窗口或对话框应用程序,并在程序中使用Console。¤

关于Console更详细的主题,请参阅MSDN

“Consoles”一节内容。Windows进程¤

什么是进程¤

进程与线程的关系¤

进程的属性¤

创建一个进程:CreateProcess函数¤

终止进程¤

子进程Windows进程1.

什么是进程–

进程可以定义为应用程序的一个运行实例,在Win32中,每个进程拥有4GB的地址空间,用来存放应用程序的EXE文件以及相关DLL文件所需的代码和数据。每个进程还拥有别的资源比如文件、动态内存分配和线程。这些不同的资源在进程的生命中被创建,当进程终止时,它们也被释放。Win32进程是惰性的,也就是说,Win32进程什么都不执行,进程想要完成一定的工作就必须拥有线程。Windows进程2.

进程和线程的关系–

每个Win32进程至少拥有一个线程,线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,一个进程内的所有

线程使用同一个地址空间,并且共享所有的进程资源,操作系统为每个独立的线程进行CPU时间调度,类似与多任务的情况,由于线程每次消耗的时间片足够短,宏观上所有的线程同时执行。Windows进程–当Win32进程被创建时,它的第一个线程,我们称之为主线程,是由系统自动产生的。主线程或由主线程创建的线程能够创建更多的线程。如果在进程的地址空间中没有线程在执行,进程就没有存在的必要了,系统会自动释放进程和它的地址空间。提示:Windows

NT/2K等可以使用含有多个CPU的计算机,多个线程可

以同时在多个CPU上真正的同时运行。而Windows

95只能利用一个CPU,及时计算机中含有多个CPU,Windows

95也只能一次调度一个线程,其它的CPU处于休眠状态。Windows进程:实例句柄进程的属性进程的实例句柄:每个加载进进程空间的EXE或DLL文件都被赋给一个唯一的实例句柄。EXE文件的实例句柄作为

WinMain的第一个参数hinstExe传递,在调用装入资源的函数时通常需要该句柄的值。比如要从

EXE文件的映象中装入一个图标资源,做如下调用:LoadIcon(HINSTANCE

hinst,LPCTSTR

lpszIcon);进程句柄的实际值是个基本内存地址,它指出系统是在何处把EXE文件的映象装入进程的地址空间的。例如,如果系统在地址0x00400000处装入可执行文件的内容,WinMain的hinstExe参数的值就是0x00400000。Windows进程:实例句柄Win32API函数ModuleHandle的返回值指出EXE或DLL文件装在进程地址空间的基本地址,该函数原型如下:HMODULE

GetModuleHandle(LPCTSTR

lpszModule);注意:Win32API不区分一个进程的HMODULE和HINSTANCE值,它们是同一个。在Win32关于某个函数的文档中指出需要HMODULE时,可以传给HINSTANCE,反之亦然。提示:装入应用程序的基本地址事由链接器决定的,不同的链接器可能使用不同的缺省基本地址,Visual

C++的链接器使用的缺省基本地址是

0x00400000,这是运行在Windows

95时可执行文件的映射能装入的最低地址。对于Microsoft的链接器,可以使用/BASE:address链接开关来改变应用程序的基本装入地址。Windows进程:实例句柄2)

进程的前一个实例句柄:C/C++运行时启动代码总是将NULL传递给WinMain的hinstExePrev参数,该参数的存在是为了向前兼容,对Win32应用来说没有意义。在16位Windows应用程序里,hinstExePrev给出

的是同一应用程序的另一个实例句柄,如果没有其它实例在运行,该值被设为NULL。16位Windows环境下,应用程序可以使用该参数来判断是否有另一个实例在运行,因为根据不同的应用,有些程序可能不允许有多个实例运行。因此在Win32环境下,我们需要使用其它手段来检查是否有多个实例在运行。Windows进程:实例句柄¤检查多个实例的方法:应用程序要检查是否有多个实例在运行,可以有很多办法,原则在于,只要可以找到一个对于应用程序的多个实例来讲是一个全局的东西来进行标记就可以了。比如:内核对象可以认为是全局的,每个实例都试图创建一个新的内核对象,当发现该对象已经被创建时,即表示有其它实例在运行。类似于内核对象,我们还可以使用注册表、磁盘文件、甚至系统的环境变量等来进行标记。另一种办法对于具有窗体的应用程序可用(即使其窗体是隐藏的),Win32函数FindWindow可以根据窗口标题来查找一个窗口,如果返回NULL,则表示该窗口不存在,即当前还没有实例在运行。Windows进程:实例句柄¤这里我们再介绍另一种方法来判断当前有几个应用程序的实例在运行。下面的这段代码显示了如何在应用程序的多个实例中共享一个变量:#pragma

data_seg(“Shared”)LONG

g_lUsageCount

=

-1;#pragma

data_seg();#pragma

comment(linker,

“/section:Shared,

rws)¤

这段代码指明了在#pragma

data_seg界定的Shared段中

的变量g_lUsageCount可以被多个实例读写共享(rws)。之后在应用程序中可以通过安全的访问对该变量进行读

写,从而判断当前有几个实例在运行。范例2:Multiple

Instance¤

练习:用上面提到的任意一种方法编写只允许一个实例运行的应用程序。范例MultInst代码演示了使用实例间共享代码段的方法。¤

好的做法:对于只允许一个实例运行的具有窗体的应用程序,好的做法是如果已经运行了的实例窗体最小化或不是前台窗体,尝试再次运行时应拒绝另一个实例运行,并将正在运行的实例窗体最大化并将其设置为前台窗体(调用

SetForegroundWindow)。Windows进程:命令行3)

进程的命令行:当新进程被创建时,被传递了命令行,即

WinMain的第三个参数:LPSTRlpszCmdLine。这里要记住重要的一点是参数lpszCmdLine总是指向一个ANSI字符串,因为系统并不知道编程者喜欢用ANSI还是用Unicode,Microsoft选择了总是传递一个ANSI字符串。Win32API函数

GetCommandLine可以得到指向含有进程的完整命令行的缓冲区的一个指针,该函数原型如下:

LPTSTR

GetCommandLine(VOID);提示:因为lpszCmdLine是LPSTR而不是LPCSTR,所以可以向它指向的缓冲区中写入数据,但无论如何不能写过缓冲区边界!因此最好把它当作一个只读的缓冲区,需要改变命令行时,好的方式是把它拷贝到一个本地缓冲区进行操作。Windows进程:环境变量4)

进程的环境变量每个进程都有一个与它相关的环境块。环境块是进程的地址空间中分配的一个内存块。每个环境块都包含一组字符串,其形式如下:每个字符串的第一部分是环境变量的名字,后跟一个等号,等号后面是要赋予变量的值。环境块中的所有字符串都必须按环境变量名的字母顺序进行排序。Windows进程:环境变量应用程序可以调用下列函数使用环境变量GetEnvironmentVariable就能够确定某个环境变量是否存在以及它的值DWORD

GetEnvironmentVariable(

LPCTSTR

lpName,LPTSTR

lpBuffer,

DWORD

nSize

)ExpandEnvironmentStrings显示可取代的字符串,例如%USERPROFILE%,可以通过该函数对这种类型的字符串替换。DWORD

ExpandEnvironmentStrings(

LPCTSTR

lpSrc,LPTSTR

lpDst,

DWORD

nSize

)SetEnvironmentVariable函数来添加变量、删除变量或者修改变量的值BOOL

SetEnvironmentVariable(

LPCTSTR

lpName,LPCTSTR

lpValue

)Windows进程:错误模式5)进程的错误模式:每个进程都有相关联的一组标志用于告诉系统进程对严

重的错误应该如何作出反映,这包括磁盘介质故障、未处理

的异常情况、文件查找失败和数据没有对齐等。进程可以告

诉系统如何处理每一种错误,方法是调用SetErrorMode函数。默认情况下,子进程继承父进程的错误模式标志。即,

如果该进程的SEM_NOGPFAULTERRORBOX标志被打开,那么它

的子进程程也拥有这个打开的标志。但是,子进程并不知道

这个情况,因此该子进程尚未编写处理GP故障的错误。如果

GP故障发生在子进程的某个线程中,该子进程就会终止运行,而不通知用户。父进程可以防止子进程继承它的错误模式,方法是在调用CreateProcess时设定CREATE_DEFAULT_ERROR_MODE标志。Windows进程:当前路径6)

进程的当前路径当不提供全路径名时,Windows的各个函数就会在当前驱动器的当前目录中查找文件和目录。例如,如果进程中的一个线程调用CreateFile来打开一

个文件(不设定全路径名),那么系统就会在当

前驱动器和目录中查找该文件。系统将在内部保

持对进程的当前驱动器和目录的跟踪。由于该信

息是按每个进程来维护的,因此改变当前驱动器

或目录的进程中的线程,就可以为该进程中的所

有线程改变这些信息。通过调用下面两个函数,

线程能够获得和设置它的进程的当前驱动器和目

录:GetCurrentDirectorySetCurrentDirectoryWindows进程:当前路径¤

通过调用GetFullPathName,父进程可以获得它的当前目录。例如若要获得驱动器C的当前目录,可以像下面这样调用GetFullPathName:char

buf[MAX_PATH]

=

{0};GetFullPathName("C:",

MAX_PATH,

buf,

NULL);Windows进程:当前路径¤对于磁盘驱动器的当前目录,进程可以有类似如下的环境变量:=C:=C:\Utility\bin=D:=D:\Program

Files=E:=E:¤当你调用CreateFile试图打开一个不使用全路径名的文件名时,系统将查找磁盘的当前目录。考虑如下两行代码的差别:HANDLE

hFile

=

CreateFile("D:Readme.txt“,

…);HANDLE

hFile

=

CreateFile("D:\\Readme.txt“,

…);¤

请注意这里的文件名:D:Readme.txt。如果使用全路径文件名:

D:\\Readme.txt,则系统将去D:盘根目录下查找Readme.txt,而对于文件名D:Readme.txt,它并不是一个全路径文件名,系统将在D:盘的当前目录下查找并打开Readme.txt。范例3:Process

Properties¤

检查进程的环境变量并深刻理解当前路径和当前目录的概念。¤

范例ProcessProp中,演示了访问并向

Console输出进程环境变量的情况,并根据是否使用全路径文件名的情况打开文

件,揭示了磁盘驱动器当前目录的含义。Windows进程:CreateProcess4.

创建一个进程:CreateProcess函数当一个线程调用CreateProcess时,系统就会创建一个进程内核对象,其初始使用计数是1。该进程内核对象不是进程本身,而是操作系统管理进程时使用的一个较小的数据结构。可以将进程内核对象视为由进程的统计信息组成的一个较小的数据结构。然后,系统为新进程创建一个虚拟地址空间,并将可执行文件或任何必要的DLL文件的代码和数据加载到该进程的地址空间中。然后,系统为新进程的主线程创建一个线程内核对象(其使用计数为1)。Windows进程:CreateProcess¤

与进程内核对象一样,线程内核对象也是操作系统用来管理线程的小型数据结构。通过执行

C/C++运行期启动代码,该主线程便开始运行,它最终调用WinMain函数。如果系统成功地创

建了新进程和主线程,函数返回TRUE。¤

注意在进程被完全初始化之前,

CreateProcess便返回TRUE。这意味着操作系

统加载程序此时尚未试图找出所有需要的DLL。如果一个DLL无法找到,或者未能正确地初始

化,那么该进程就终止运行。因此

CreateProcess返回TRUE时,父进程并不知道

出现的任何初始化问题。Windows进程:CreateProcess¤

函数原型:Windows进程:CreateProcess¤

lpApplicationName和lpCommandLine这两个参数分别用于设定新进程将要使用的可执行文件的名字和传递给新进程的命令行字符串,设定lpApplicationName为NULL,而使用

lpCommandLine参数设定一个完整的命令行,以便

CreateProcess用来创建新进程。如果lpApplicationName不为NULL,可以将地址传递给参数中包含想运行的可执行文件的名字的字符串。请注意,必须设定文件的扩展名,系统将不会自动假设文件名有exe扩展名。Windows进程:CreateProcess¤

lpProcessAttributes和lpThreadAttributes,

bInheritHandles若要创建一个新进程,系统必须创建一个进程内核对象和一个线程内核对象(用于进程的主线程),由于这些都是内核对象,因此父进程可以得到机会将安全属性与这两个对象关联起来。可以使用

lpProcessAttributes和lpThreadAttributes参数分别设定进程对象和线程对象需要的安全性。当参数设为NULL,系统为这些对象赋予默认安全性描述符。也可以指定两个SECURITY_ATTRIBUTES结构,并对它们进行初始化,以便创建自己的安全性权限,并将它们赋予进程对象和线程对象。bInheritHandles为TRUE时,子进程可以继承父进程的任何可继承句柄,反之,将不能继承任何句柄。Windows进程:CreateProcess¤

dwCreationFlagsdwCreationFlags参数用于标识标志,以便用于规定如何来创建新进程。如果将标志逐位用OR操作符组合起来的话,就可以设定多个标志。dwCreationFlags参数也可以用来设定优先级类。不过用不着这样做,并且对于大多数应用程序来说不应该这样做,因为系统会为新进程赋予一个默认优先级。Windows进程:CreateProcess¤

lpEnvironmentlpEnvironment参数用于指向包含新进程将要使用的环境字符串的内存块。在大多数情况下,为该参数传递NULL,使子进程能够继承它的父进程正在使用的一组环境字符串。也可以使用

GetEnvironmentStrings函数,该函数用于获得调用进程正在使用的环境字符串数据块的地址。可以使用该函数返回的地址,作为CreateProcess的lpEnvironment参数。如果为该参数传递NULL,那么这正是CreateProcess函数所做的操作。当不再需要该内存块时,应该调用FreeEnvironmentStrings函数将内存块释放。Windows进程:CreateProcess¤

lpCurrentDirectorylpCurrentDirectory参数允许父进程设置子进程的当前驱动器和目录。如果本参数是NULL,则新进程的工作目录将与生成新进程的应用程序的目录相同,如果本参数不是NULL,那么lpCurrentDirectory必须指向包含需要的工作驱动器和工作目录的以

0结尾的字符串。注意,必须设定路径中的驱动器名。Windows进程:CreateProcess¤

lpStartupInfo当Windows创建新进程时,它将使用该结构的有关成员。大多数应用程序将要求生成的应用程序仅仅使用默认值。Windows进程:CreateProcess¤

lpProcessInformation参数用于指向你必须指定的PROCESS_INFORMATION结构。CreateProcess在返回之前要对该结构的成员进行初始化。创建新进程可使系统建立一个进程内核对象和一个线程内核对象。在创建进程的时候,系统为每个对象赋予一个初始使用计数值1。然后,在

CreateProcess返回之前,该函数打开进程对象和线程对象,并将每个对象的与进程相关的句柄放入

PROCESS_INFORMATION结构的hProcess和hThread成员中。当CreateProcess在内部打开这些对象时,每个对象的使用计数就变为2。Windows进程:终止进程终止进程若要终止进程的运行,可以使用下面四种方法:主线程的进入点函数返回(最好的方法)。进程中的一个线程调用ExitProcess函数(应该避免使用这种方法)。另一个进程中的线程调用

TerminateProcess函数(应该避免使用这种方法)。进程中的所有线程自行终止运行。Windows进程:终止进程¤

主线程的进入点函数返回即只有当主线程的进入点函数返回时,它的进程才终止运行。这是保证所有线程资源能够得到正确清除的唯一办法。让主线程的进入点函数返回,可以确保下列操作的实现:该线程创建的任何C++对象将能使用它们的析构函数正确地撤消。操作系统将能正确地释放该线程的堆栈使用的内存。系统将进程的退出代码(在进程的内核对象中维护)设置为进入点函数的返回值。系统将进程内核对象的返回值递减1。Windows进程:终止进程¤

使用ExitProcess函数该函数用于终止进程的运行,并将进程的退出代码设置为ExitCode。ExitProcess函数并不返回任何值,因为进程已经终止运行。如果在调用

ExitProcess之后又增加了什么代码,那么该代码将永远不会运行。Windows进程:终止进程¤

使用TerminateProcess函数该函数与ExitProcess有一个很大的差别,那就是任何线程都可以调用TerminateProcess来终止另一个进程或它自己的进程的运行。hProcess参数用于标识要终止运行的进程的句柄。当进程终止运行时,它的退出代码将成为ExitCode参数来传递的值。只有当无法用另一种方法来迫使进程退出时,才应该使用TerminateProcess。终止运行的进程绝对得不到关于它将终止运行的任何通知,因为应用程序无法正确地清除,并且不能避免自己被撤消(除非通过正常的安全机制)。Windows进程:终止进程¤

进程终止运行出现的情况:当进程终止运行时,下列操作将启动运行:进程中剩余的所有线程全部终止运行。进程指定的所有用户对象和GDI对象均被释放,所有内核对象均被关闭(如果没有其他进程打开它们的句柄,那么这些

内核对象将被撤消。但是,如果其他进程打开了它们的句柄,内核对象将不会撤消)。进程的退出代码将从STILL_ACTIVE改为传递给ExitProcess或TerminateProcess的代码。进程内核对象的状态变成收到通知的状态。系统中的其他线程可以挂起,直到进程终止运行。进程内核对象的使用计数递减1。可以通过调用GetExitCodeProcess来获得目前已经撤消的进程的退出代码。Windows进程:子进程6.

子进程当在设计应用程序时,可能会遇到这样的情况,

即想要另一个代码块来执行操作。通过调用函数或

子例程,可以一直象这样分配工作。当调用一个函

数时,在函数返回之前,代码将无法继续进行操作。大多数情况下,需要实施这种单任务同步。让另一

个代码块来执行操作的另一种方法是在进程中创建

一个新线程,并让它帮助进行操作。这样,当其他

线程在执行需要的操作时,代码就能继续进行它的

处理。这种方法很有用,不过,当线程需要查看新

线程的结果时,它会产生同步

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论