一个进程的实例把手(句柄)
每一个被加载进程的地址空间的可执行文件或者动链库文件被分配给一个独一无二的实例把手。你的执行文件的实例被传送作为WinMain的第一个参数,hInstanceExe。把手的值主要被调用来加载资源。例如,用来加载一个图标资源
Hicon loadicon(hinstance hinstance,pctstr pszIcon);
Loadicon的第一个参数指示哪一个可执行或动链库文件包含你想要加载的资源。许多程序把WinMain的hinstanceExe参数保存在一个全局变量里面,以便访问起可执行文件的代码来更容易。
平台的软件开发包文档这样描述:一些函数需要HMODULE类型的参数。比如,GetModuleFileName函数,
DWORD GetModuleFileName
(HMODULE hInstModule,PTSTR pszPath,DWORD cchPath);
注意,事实证明,HMODULE和HINSTANCE完全相同的东西。如果函数的文档指明需要HMODULE,你可以传送一个HINSTANCE,反之亦然。有两个数据类型的原因是在16位操作系统中(目前普遍是32位,将来可能会64位)这两种类型标识不同的东西。
WinMain的参数hInstanceExe的真实值是可执行文件的镜像被系统加载到进程地址空间的基址。例如,如果系统打开可执行文件并且加载它的内容到地址0x00400000,WinMain的参数hInstanceExe就有了一个0x00400000的值。
执文(可执行文件)镜像加载到的基址由链接器决定。不同的链接器会用不同的默认基址。因为历史的原因,视平(可视化平台)链接器使用值为0x00400000的默认基址:这是你运行98程序时,执文镜像能加载到的最低的地址。对于微软的链接器,使用/BASE:address链接器开关,你能改变你程序加载的基址。随后展示GetModuleHandle函数,返回把手/返回执文动文被载到进程的地址空间的基址。
HMODULE GetModuleHandle(PCTSTR pszModule);point const terminate string 指向常量的最后一个字符为零的字符串的指针。Char* 或者wchar* Tchar* (unsigned int *)?
当你调用这个函数时,你把指定了被加载到进程地址空间中的执文或动文名称的零终字符串传入。如果系统发现了指定的执文或动文名称,函数返回执文或动文镜像被加载的基址。如果系统不能发现文件名则返回NULL。你也可以在调用GetModuleHandle时传送NULL,返回值为调用者所在执文的基址。
如果代码在动文里,你有两种办法知道你的代码正运行在哪一个模块。首先,你可利用链接器体统的伪变量__ImageBase,指向当前运行模块的基址。如前面讨论的,当c运行环境开始代码调用你的入口函数WinMain时所做的。
另一个选择是使用GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS为第一个参数调用GetModuleHandleEx函数,当前函数的地址作为第二个参数。指向HMODULE被作为最后一个参数传递的的指针被函数使用“包含传入函数的动文的相应的基址”填充。
//code//
记住GetModuleHandle函数的两个重要特征。第一,它仅仅检查调用中进程的地址空间。如果调用中进程没有使用任何通用对话框函数,调用GetModuleHandle并且传送ComDlg32会导致返回NULL,尽管ComDlg32.dll动文可能被载入其他程序的地址空间。第二,调用GetModuleHandle 并且传送一个空值NULL,返回进程地址空间中的执文基址。所以甚至你从包含在动文中的代码中调用GetModuleHandle(NULL),返回值仍然是执文的基址而不是动文的。
进程的前实例把手
以前提到,c/c++运行时开始代码也传送NULL给WinMain的hPrevInstance参数。这个参数被16位Windows使用,仍然作为WinMain的参数仅仅为了16位Windows应用程序容易移植。你永不要在你的代码中引用这个参数。基于这个原因,我总是这样写我的WinMain函数:
Int WINAPI _tWinMain(HINSTANCE hInstance,HINSTANCE,PSTR pszCmdLine,int nCmdShow);
因为第二个参数没有给出参数名,编译器不发出“参数未被引用”警告。视平选择了不同的解决方法:产生c++图接项目向导定义采用UNREFERENCED_PARAMETER宏移除警告,下列代码段展示:
//code//
进程的命令行
当新进程被创建,被传送一个命令行。几乎从不空;最常见的,创建新进程的执文名称是第一个记号。然而,正如你以后当我们讨论CreateProcess看到的,进程能接收单字符组成的字符串命令行。当c运行时开始代码开始执行一个图接应用程序,通过调用GetCommandLine windows函数接到进程的完整命令行,跳过执文名称,传递指向命令行剩下的部分的指针给WinMain的pszCmdLIne参数。
应用程序解析翻译命令行字符串为任何选择的方式。你能真实写入被pszCmdLine参数指向的内存缓冲,但是你不该这样做,在任何环境下,在缓存外面写。个人建议,我总是把这个缓存当作只读缓存。如果我想要改变命令行,我首先拷贝命令行缓存到程序中的本地缓存,然后我改变我的本地缓存。
照着C运行时的例子,通过调用GetCommandLine你能获得指向你的进程的完整命令行的指针:
这个函数返回指向包含完整命令行(包含执文的完整路径名)的缓存的指针。小心这个函数返回同一块缓存的地址。这是另一个你不应该改写pszCmdLine的原因:因为都是那一块内存,改写完了后,你不知道原来命令行是什么了。
很多应用程序喜欢解析命令行到分开的元素,通过使用全局__argc和__argv变量,应用程序能得到命令行单个部分的访问权,虽然他们被推荐不使用。下面的函数commandLineToArgvW在shellapi.h头文件中声明,从shell32.dll中出口,分开任意unicode字符串到离散元素:
像函数名尾部W暗示的,函数仅仅在unicode版本使用。第一个参数,pszCmdLine,指向命令行字符串。通常是调用GetCommandLineW的返回值。pNumArgs参数是整型地址;整型设置为命令行中的变量参数个数。函数返回unicode字符串指针的数组的地址。
函数内在的分配内存。多数应用程序不释放这个内存他们预计操作系统释放内存当进程终结。这完全可以接受。然而,如果你想要自己释放内存,正确的方法是调用heapfree.
HeapFree(GetProcessHeap(),0,ppArgv(返回的数组))
进程的环境变量
每一个进程有一个相应的环境块。环境块是一块在进程地址空间内部分配的内存包含一套字符串像下文这样:
=::=::/...
VarName1=VarValue1/0
…………
….
/0
每个字符串的第一部分是环境变量的名称。随后是赋值符号,随后是你想要赋给变量的值。额外注意到第一个=::=::/字符串,块中的一些其他字符串开始与=字符。这个例子,这些字符串不是用作环境变量,你不久就会在进程的当前目录里看到。
//code//
=符号开始的无效的字符串被略过。其他每一个有效的字符串被一条条解析。=字符被用作分割名称和值的分隔符。当你不再需要函数GetEnvironmentStrings返回的内存块,你该调用FreeEnviromentStrings释放内存块。
注意在代码片段中使用的c运行时的安全字符串函数,利用比特为单位用StringcbCopyN大小计算和当值对于拷贝缓存来说太长时用StringCchCopyN截断。
第二种访问环境变量的方法仅仅对控接应用程序有效通过被main入口点函数棘手到的TChar*env[]参数。不想GetEnvironmentStrings返回值,env是一个字符串指针数组,每一个指针到不同定义成“名称=值“格式的环境变量。在指向最后一个变量字符串的指针之后一个空指针出现。
//code//
注意=字符开始的怪字符串在你收到env之前被删除了,所以你不需要自己处理他们。因为等号被用来把名称和值分开,等号不能是名称的一部分。同样空格是显性的。例如,如果你声明下列两个变量并且使用abc的值比较xyz的值,系统将报告,两个变量不用因为任何空格出现在等号之前或之后被考虑到计数。
XYZ= WINDOWS;
ABC= WINDOWS;
例如,如果你将家下列两个字符串到环境块中,带一个空格的环境变量xyz在后面将包含home并且不带有空格的环境变量xyz将包含work
当一个用户登录到windows,系统创造解释进程并且关联一个套环境字符串给用户。系统获取初始环境变量的集合通过检测两个注册表中的两个keys
HEY_LOCLA_MACHINE/SYTEM/CurrentControlSet/Control/Session Manager/Environmen
包含应用给系统的所有环境变量列表
第二个key包含应用到当前登录用户的所有环境变量的列表
HKEY_CURRENT_USER/Environment
用户可以添加,删除或者改变任意之一条目通过控制面板选择系统,点击左边的高级系统设置连接,点击环境变量按钮打开下列对话框。
仅仅有Administrator权限的用户能改变系统变量列表的变量。
你的程序也可以使用变量注册函数改变这些注册条目。然而为了是变化应用到所有应用程序,用户必须离开再登录。一些应用程序,诸如:资源管理器,任务管理器,控制面板,当他们的主视窗接收到WM_SETTINGCHANGE消息时,能用心的注册的条目更新他们的环境块。例如,如果你更新了注册条目并且想有兴趣于应用程序更新他们的环境块,你可以这样调用。
SendMessage(HWND_BROADCAST,WM_SETTINGCHANGE,0,(LPARAM)TEXT(“Enviroment”));
通常,子进程继承一套和他的父进程相同的环境变量。然而,父进程能控制子进程继承什么环境变量。当我们讨论创建进程函数时你会看到。通过继承,子进程得到父进程的环境块的它自己的拷贝。子父不共享同一块。这意味着子进程能在他的环境块中添加删除,改变变量并且改变不会反映到父块中。
应用程序通常使用环境变量来让用户调整自己的行为。用户创建爱你环境变量并且初始化。然后,当用户调用应用程序,应用程序检测环境变量块中的变量。如果应用程序发现变量,应用程序解析它的值调整它自己的行为。
环境变量的问题是他们不容易为用户设置或者理解。用户需要正确的拼写变量名称,并且他们必须也知道变量的值的期待的正确语法。多数(也许是所有)图接应用程序,用另外一种方式,允许用户使用对话框调整应用程序的行为。这种方法更友好。
当我们调用GetEnvironmentVarialbe pszName指向需要的变量名,pszvalue指向将要持有变量值的缓存,并且cchValue以字符为单位指示缓存的尺寸。函数返回拷贝到缓存的字符的数字或者如果变量名称找不到就返回0。然而因为你不知道存储一个环境变量需要多少字符,GetEnvironmentVariable 返回字符的数字加上最终Null字符,当0被传送到cchValue参数。下列的代码演示了如何安全使用这个函数
//code//
许多字符串包含可替换字符串。例如,我在注册中发现这个字符串
%USERPROFILE%/Documents
显示在百分号之间的部分指示了一个可替换字符串,这里,环境变量的值,USERPROFILE,该被替换为这个字符串,在我的机器里,c:/users/jrichter
所以,应用了字符串替换后,结果字符串变成
C:/users/jrichter/document
因为这种字符串替换很普遍,windows提供了expandEnvironmentStrings函数
当你调用这个函数,pszSrc参数是包含可替换环境变量字符串的字符串的地址。Pszdst参数是接收扩展字符串的缓存的地址,chsize参数是缓存的字符数尺寸。返回值是需要存储扩展的字符串的缓存的尺寸用字符数计算。所以你通常调用函数两次。
最后,你能使用SetEnvironmentVarible函数添加变量,删除变量,改变变量的值。函数用pszValue参数改变用pszname参数标志的变量的值。如果特定的变量不存在,变量被添加如,如果值是null,变量被从环境快中删除
你应该总是使用这些函数维持你的程序的环境块。
进程的关联
通常,进程中的线程执行在任意一个在主机上的cpu上。然而,进程的线程能被强迫在有效cpu的子集上运行。这叫做处理器关联并且在7章讨论,线程规划:优先和关联。子进程集成父进程的关联性
进程的错误模式
与每一个进程相关的是一组告诉系统“进程该怎样对包括磁盘媒介错误无法处理的异常文件发现错误和数据错误赋值等严重错误负责”的标志。进程能通过调用SetErrorMode告诉系统怎样处理这些错误中的每一个。
Table 4-3: SetErrorMode 的标志 |
|
标志 |
Description |
SEM_FAILCRITICALERRORS SET ERROR MODE |
系统不显示处理 critical-error-handler 消息框 返回错误到调用的进程。 |
SEM_NOGPFAULTERRORBOX |
系统不显示处理general-protection-fault 消息框,仅仅调试自己使用意外处理来处理通常保护错误的应用程序时设置此标志, |
SEM_NOOPENFILEERRORBOX |
不显示消息框当找不到文件时。 |
SEM_NOALIGNMENTFAULTEXCEPT |
系统自动修复内存对齐错误,使他们对应用程序不可见,对X86/64处理器无效 |
默认的子进程集成父进程的错误模式标志。换句话说,如果一个不显示通常保护错误的标志打开的进程,衍生出一个子进程,子进程将也有打开的这个标志。然而,子进程不被通知这件事,并且可能没有被写处理gp错误语句。如果一个gp错误发生在子进程的一个线程中,子进程可能没有任何通知用户就终结了。父进程能防止子进程继承错误模式通过指定CREATE_DEFAULT_ERROR_MODE标志当调用createProcess
进程的当前驱动器和目录
当没有提供全路径名的时候,多数windows函数在当前驱动器的当前目录下寻找文件。例如,如果进程中的一个线程调用CreateFile函数来打开一个文件没有指定完全文件名,系统在当前驱动器和目录下查找文件。
系统内在的保持追踪进程当前驱动器和目录。因为这些信息被维持在一个基于每个进程,进程中的一个线程改变当前驱动器和目录改变进程中所有线程的。
线程能通过调用下面两个函数获得和设置进程的当前驱动器和目录
GetCurrentDirctory
SetCurrentDirectory
如果你提供的缓存不够大,得函数返回存储这个文件夹需要的字符数量,包括零终,并且不拷贝东西到提供的缓存中,这里可以设为NULL。调用成功时,不计算零终的字符数长度被返回。
注意MAX_PATH常量在windef.h中定义为260是目录名或者文件名的最大字符数。所以调用得到函数时,传送max_pathtchar类型元素的缓存是安全的。
进程的当前目录
系统保持追踪进程的当前驱动和目录,但是不为每一个驱动器保持追踪当前目录。然而,有些操作系统为多驱动器当前目录处理提供支持。这些支持通过进程的环境字符串实现。例如,进程能有两个环境变量
=c:=c:/utility/bin
=d:=d:/program files
这些变量指定了进程的c驱动器当前目录是/utility/bit并且d驱动器的当前目录是/program file。如果你调用一个函数,传送驱动限定名称指定一个非当前的驱动器,系统查找进程的环境变量块找相关与指定驱动器符号的变量。如果为驱动器的变量存在,系统使用变量值作为当前目录。如果变量不存在,系统假设指定驱动器的当前目录是根目录。
例如,如果你的进程的当前目录是c:/utility/bin 你调用Createfile来打开D:ReadMe。Txt系统查找环境变量=D:。因为变量存在,系统尝试从D:/program file打开ReadMe。如果=D:变量不存在,系统将尝试在D驱动器的根目录上打开Readmetxt。Windows文件函数从不佳或者改变驱动器符号环境变量,仅仅读取变量。
注意你能使用c运行时函数_chdir代替windows的SetCurrentDirctory函数改变当前目录,_chdir函数内在的掉用友面的函数,但是它也添加或改变环境变量通过调用设置环境变量函数,所以,不同驱动器的当前目录被保存下来。
如果父进程创建环境块想传给子进程。子进程环境块不自动继承父进程的当前目录。而是,字集成当前的目录默认到每一个驱动器的根目录。如果你想要子进程集成父进程的当前目录,父进程必须创建这些驱动器符号环境变量,并且添加到环境模块中在衍生出字集成之前。父进程能获取他当前的目录通过调用GetfullPathName
例如,获取当前的c驱动器的目录,
函数调用,结果,驱动符号环境变量通常必须被放置在环境块的开始。
系统版本
频繁的,应用程序需要判断用户运行在哪一个windows的版本上。例如,一个应用程序可能通过调用指定的函数诸如:CreateFileTransacted利用windows事务处理的文件系统特色。然而,函数仅仅在vista上能完全应用。
根据我能记忆起的,windows应用程序编程接口已经有一个GetVersion函数这个函数很有一段历史。他首先是一个为16位windows设计的。主意很简单,返回msdos版本号在高字节,返回widnows版本号在低字节。对每一个字节,高比特表示主版本号码,低比特表示副版本号码
不幸的是,写这个代码的程序员出了点小问题,编写这个函数windows版本数被反转了,主版本号码在低比特并且副版本号在高比特。因为很多程序员已经开始使用这个函数了,微软被迫放着就那么着了,并且改变文档反映这一错误。
因为围绕着GetVersion的所有的混乱,微软添加了一个新的函数,GetVersionEx
这个函数需要你分配一个OSVERSIONINFOREX结构在你的程序中,传结构的地址给函数。
结构从windows2000开始有效。其他的版本使用旧的结构,没有服务包,套件掩码,产品类型,和保留成员。
注意结构对于每一个系统的版本号码的成分有不同的成员。这样做以便于程序员将不会不得不烦恼来拆开低字节,高字节,低,高比特,这使得应用程序和主机系统的版本号码比较他们的期望版本号码更容易
Table 4-4: The OSVERSIONINFOEX 结构成员 |
|
成员 |
描述 |
dwOSVersionInfoSize DOUBLE WORD OPERATE SYSTEM |
必须设置自sizeof(OSVERSIONINFO) 或sizeof(OSVERSIONINFOEX) 优先于调用GetVersionEx 函数 |
dwMajorVersion |
主机主版本 |
dwMinorVersion |
主机父版本 |
dwBuildNumber |
当前系统构建号 |
dwPlatformId |
当前系统支持的平台, 可以是VER_PLATFORM_WIN32s (Win32s), VER_PLATFORM_WIN32_WINDOWS (Windows 95/Windows 98), or VER_PLATFORM_WIN32_NT (Windows NT/Windows 2000, Windows XP, Windows Server 2003, and Windows Vista). |
szCSDVersion |
这个域包含着安装的操作系统的更多的信息。 |
wServicePackMajor |
最新安装包的主版本 |
wServicePackMinor |
最新安装包的副版本 |
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, VER_SUITE_ SINGLEUSERTS (单终端服务会话每个用户), VER_SUITE_PERSONAL (使vista家庭和专业版不同), VER_SUITE_BLADE, VER_SUITE_ EMBEDDED_RESTRICTED, VER_SUITE_SECURITY_APPLIANCE, VER_SUITE_STORAGE_SERVER, 和VER_SUITE_COMPUTE_SERVER). |
wProductType |
定义哪一个操作系统 VER_NT_WORKSTATION, VER_NT_SERVER, or VER_NT_ DOMAIN_CONTROLLER. |
wReserved |
保留将来使用 |
得到系统版本,提供了详细的代码例子,基于结构,显示给你怎样揭秘结构的字段。
为了让事情更容易,vista提供了函数verifyversioninfo,使用你的程序需要的版本比较主机的版本
为了使用这个函数,你必须分配一个结构osversioninforex,使用sizeof加结构初始化他的dwosversioninfosize成员,并且初始化对你的程序重要的结构的任何其他成员。当你调用比校验版本函数,dwtypemask参数指示你已经初始化了结构的哪些成员。参数是任意下面的标志或到一起的;
VER_MINORVERSION, VER_MAJORVERSION, VER_ BUILDNUMBER, VER_PLATFORMID, VER_SERVICEPACKMINOR, VER_SERVICEPACKMAJOR, VER_SUITENAME, and VER_PRODUCT_TYPE
最后一个参数,dwlConditionMask,是一个64位值,控制函数如何分析系统的版本信息到你的需要的信息
它描述了使用位合并的复杂的集合分析。为创建需要的位合体,你需要VER_SET_CONDITION宏
第一个参数,dwlconditionMask,指定你要操纵位的变量。注意你不传递变量的地址。DwTypebitmask参数只是一个简单的成员在结构中你想要比较的。为了比较多成员,你必须调用多次宏,一次一个成员。你传给校验函数的参数dwtypemask的标志(ver_minorversion,ver_buildnumber)和你使用在宏的dwtypebitmask参数的标志一样。
Ver_set_condition的最后一个参数,dwconditionmask指示,你想分析变成怎样。可以是下列值,ver_ equal,greater,greater_equal,less less_equal,注意当你比较ver——product——type信息时,你能使用这些值例如:ver_nt_workstation小于ver_nt_server。然而,对于ver_suitname信息,你不能使用这些测试值,而是,你必须使用ver_and(所有的套件产品必须被安装)或者ver_or(至少套件产品的一个被安装)
在你建立一套条件之后,你调用校验函数,如果成功他返回非零的值(如果主机系统满足所有的应用程序需求)如果校验函数返回0,主机系统不满足你的需求,或者你调用函数不正确。你能测试为什么返回零通过Getlasterror,如果返回ERROR_OLD_WIN_VERSION.你调用哪个的函数正确但是系统不能满足你的需求。
这是一个例子测试是否是vista
//code//