1. 进程的组成:
◆ 一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。
◆ 一个地址空间。其中包含可执行文件或DLL模块的代码和数据。此外它还包含动态内存分配,比如线程堆栈和堆的分配。
2. CUI程序的链接开关是:/SUBSYSTEM:CONSOLE
入口点函数:_tWinMain或者_tmain(具体的符号取决于我们了是否要使用Unicode字符串.操作系统实际并不调用我们所选的入口点,而是调用由C/C++运行库实现并在连接时使用-entry:命令行选项来设置)
GUI程序的链接开关是:/SUBSYSTEM:WINDOWS
入口函数:WinMain或者wWinMain函数
但错误的选择了一个项目类型的时候的解决方法:
◆ 把main函数改为WinMain.这通常不是最佳方案.
◆ 在Visual C++ 中创建一个新的Win32控制台应用程序项目,然后再新项目中添加现有的源代码模块.
◆ 在项目属性对话框中,定位到Configuration Properties(配置属性)/Linker(链接器)/System(系统)/SubSystem(子系统)选项.把/SUBSYSTEM:WINDOWS改为/SUBSYSTEM:CONSOLE.
◆ 在项目属性对话框中删除/SUBSYSTEM:WINDOWS开关.
3. crtexe.c文件的中是个启动函数的用途总结:
◆ 获取指向新进程的完整命令行的一个指针
◆ 获取指向新进程的环境变量的一个指针
◆ 初始化C/C++运行库的全局变量.如果包含了stdlib.h我们代码就可以访问这些变量
◆ 调用所有全局和静态C++类对象的构造函数.
结束后调用函数将调用C运行库函数exit,向其传递返回值(nMainRetVal).exi执行一下任务:
◆ 调用_onexit函数调用所注册的任何一个函数
◆ 调用所有全局和静态C++类对象的析构函数.
◆ 在Debug生成中,如果设置了_CRTDBG_LEAK_CHECK_DF标志,就通过调用CrtDumpMemoryLeaks函数来生成内存泄漏报告.
◆ 调用系统的ExitProcess函数,向其传入nMainRetVal.这会导致操作系统”杀死” 我们的进程,并设置它的退出代码.
注҉意҉:为了安全起见,Microsoft并不赞成使用这些变量.因为使用了这些变量的代码可能会在C运行库初始化这些变量之前运行.鉴于此,我们应该直接调用对应的API函数.
4. 加载到进程地址空间的每一个可执行文件或者DLL文件都被赋予了一个独一无二的实例句柄.可执行文件的实例被当作(w)WinMain函数的第一个参数hInstanceExe传入.在需要加载资源的函数调用中,一般都要提供此句柄的值.
另外Platform SDK文档指出,有的函数需要一个HMODULE类型的参数.例如GetModuleFileName就是如此.
其实,HMODULE和HINSTANCE完全是一回事.如果某个函数的文档指出需要一个HMODULE参数,我们可以传入一个HINSTANCE,反之亦然.之所以有两种数据类型.是由于在16位Windows中HMODULE和HINSTANCE表示不同类型的数据.
VS连接器使用的 默认基址是0x00400000.我们也可以使用Mircosoft连接器的/BASE:address连接器开关.可以更改要将应用程序加载到那个基地址.为了获取句柄/基地址,我们可以使用GetModuleHandle(PCTSTR pszModule);//其中pszModule要传递一个已在主调进程的地址中加载的一个可执行文件或DLL文件的名称.他的另外一个用法是为pzModule参数传入NULL.这样可以返回主调进程的可执行文件的基地址.
如果我们的代码在一个DLL中,那么个利用两种方法来了解代码正在什么模块中运行,可以调用GetModuleHandleEx,,将GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS作为它的第一个参数,将当前函数的地址作为第二个参数,最后一个参数是指向HMODULE的指针.GetModuleHandleEx会传入(即第二个参数)所在DLL的基地址来填写该指针.
GetModuleHandle函数的两大特征:首先,他只检查主调进程的地址空间.如果主调进程没有使用任何通用对话框.那么一旦调用GetModuleHandle,并向其传递ComDlg32,就会导致返回NULL—即使ComDlg32.dll也许已经加载到其他进程的地址空间.其次调用GetModuleHandle并向其传递NULL值.会返回进程的地址空间中的可执行文件的基地址.所以即使调用了GetModuleHandle(NULL)的代码是在一个DLL文件中,返回值仍是可执行文件的基地址.而非DLL的基地址
5.获取一个指向进程完整命令行的指针:
PTSTR GetCommandLine();//返回值包含完整的命令行(包括已执行的文件的完整路径名)
将任何Unicode字符串分解成单独的标记:
PWSTR* CommandLineToArgvW( PWSTR pszCmdLine,//GetCommandLine返回的值
Int pNumArgs);//命令行中的实参的数目
DWORD GetEnvironmentVariable( PCTSTR pszName, PTSTR pszValue,
DWORD cchValue)
pszName:指向预期的变量名称,pszValue指向保存变量值的缓冲区.而cchValue指出缓冲区大小(用字符数来表示).
返回值:如果找到环境变量,这返回复制到缓冲区的字符数;如果环境中没有找到这返回0.
进程可以调用SetErrorMode函数来告诉系统如何来处理这些错误:
UINT SetErrorMode( UINT fuErrorMode);
其中fuErrorMode参数的标志如下(个按位或)
标志 | 描述 |
SEM_FAILCRITICALERRORS | 系统不显示严重错误处理程序消息框,并将错误返回主调进程 |
SEM_NOGPFAULTERRORBOX | 系统不显示常规保护错误消息框,此标志只应该由调式程序设置:该调试程序用一个异常处理程序来自行处理常规保护错误 |
SEM_NOOPENFILEERRORBOX | 系统查找文件失败时,不显示消息框 |
SEM__NOALIGNMENTFAULTEXCEPT | 系统自动修复内存对齐错误,并使应用程序看不到这些错误.此标志对x86处理器无效 |
默认情况下,子进程会继承父进程的错误模式标志
6. 系统在内部跟踪记录着一个进程的当前驱动器和目录.由于这种信息是以进程为单位来维护的,所以假如进程中的一个线程改变了当前驱动器或目录,那么对于该进程为单位来维护的所有线程来说,此类信息是被改变了的.
可以利用SetCurrentDirectory和GetCurrentDirectory来设置和获取当前驱动器和目录
DWORD GetCurrentDirectory( DWORD cchCurDir,
PTSTR pszCurDir);
说明:如果提供的缓冲区不够大,函数将会返回保存此文件夹所需要的字符数(包括末尾的\0字符),而且不会向缓冲区复制任何内容(即为NULL).如果成功,这返回字符串的长度.
注意:winDef.h文件中被定义为260的常量MAX_PATH是目录名称或者文件名称的最大字符数.所以在调用GetCurrentDirectory的时候,向该函数传递有MAX_PATH个TCHAR类型的元素构成一个缓冲区是非常安全的.
BOOL SetCurrentDirectory(PCTSTR pszCurDir);
系统跟踪记录着进程当前驱动器和目录,但他没有记录每个驱动器的当前目录..如果调用一个函数,并且传入的路径名限定的是当前驱动器以外的驱动器,系统就会在进程的环境块中查找与指定驱动器号(也成为盘符)关联的变量.如果找到与指定驱动器号相关的变量.,系统就会将变量的值作为当前目录使用.否则就假定指定驱动器目录的当前目录就是他的根目录.
说明:可以使用C运行库函数_chdir而不是SetCurrentDirectory来更改当前目录._chdir函数在内部调用SetCurrentDirectory,但chdir还会调用SetEnvironmentVariable来添加或修改环境变量,从而使不同驱动器的当前目录得以保存.
如果一个父进程创建了一个希望传给子进程的环境块,子进程的环境块就不会自动继承父进程的当前目录.相反,子进程的当前目录默认为每个驱动器的根目录.如果希望子进程继承父进程的当前目录.父进程就必须在生成子进程之前,创建这些驱动器号环境变量,并把它们加到环境块中.父进程可以通过调用GetFullPathName来获取他的当前目录.
DWORD GetFullPathName( PCTSTR pszFile, //需要查找的驱动器号
DWORD cchPath, //pszPath的大小
PTSTR pszPath, //缓冲区
PTSTR* ppszFilePart) //用于装载文件名起始的地方
7. 系统版本获取函数:DWORD GetVersion();
微软为了避免程序员在使用时出现困惑,提供了另一个版本GetVersionEx:
BOOL GetVersionEx( POSVERSIONINFOEX pVersionInformation);
其中OSVERSIONINFOEX的结构如下:
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize; //调用前必须设置为结构体的长度
DWORD dwMajorVersion; //主机系统的主版本号
DWORD dwMinorVersion; //主机系统的次版本号
DWORD dwBuildNumber; //当前系统的构建版本
DWORD dwPlatformId; //标识当前系统支持的套件
TCHAR szCSDVersion[ 128 ]; //包含额外的文本.提供了与已安装的操作系统有关的 更多信息
DWORD wSerivcePackMajor, //最新安装的SerivcePack的主版本号
DWORD wSerivcePackMinor, //此版本号
DWORD wSuitMask , //标识当前系统上课用的suite(s)
DWORD wProductType, //指出安装的是以下系统的一个
DWORD wReserved //保留
} OSVERSIONINFO
8. 为了进一步简化编程vista还提供了VerifyVersionInfo函数,它能比较主机系统的版本和应用程序要求的版本.
BOOL VerifyVersionInfo(
POSVERSIONINFOEX pVersionInformation,//比较的版本结构体
DWORD dwTypeMask, //比较的位
DWORDLONG dwlConditionMask) //使用VER_SET_CONDITION来获取
主要是用来获取到底是要比较哪一个或那一组变量的值
VER_SET_CONDITION( DWORDLONG dwlConditionMask,//返回值
ULONG dwTypeBitMask, //和dwTypeMask一样.的标志 ULONG dwConditionMask)//比较的方式(大于,小于....)
9. CreateProcess函数精解.
BOOL CreateProcess(
PCTSTR pszApplicationName,//新进程要使用的可执行文件名
PTSTR pszCommandLine, //传给新进程的命令行参数
PSECURITY_ATTRIBUTES psaProcess,//进程安全属性
PSECURITY_ATTRIBUTES psaThread, //线程安全属性
BOOL bInheritHandles, //继承
DWORD fdwCreate, //标识了影响新进程创建方式的标志
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo)
备注:一个线程调用CreateProcess时,系统将创建一个进程内核对象,其初始使用计数为1.进程内核对象不是进程本身,而是操作系统用来管理这个进程的一个小型数据结构---可以把进程内核对象想象成由进程统计信息构成的一个小型数据结构.然后系统为新进程创建了一个虚拟地址空间,并将可执行文件(和所必要的DLL)的代码及数据加载到进程的地址空间.
然后,系统为新进程的主线程创建一个线程内核对象(使用计数为1).和进程内核对象一样,线程内核对象也是一个小型数据结构,操作系统用它来管理这个线程.这个主线程会一开始就会执行C/C++运行时的启动例程,它是由链接器设置为应用程序入口点,最终会调用应用程序WinMain,wWinMain,main或wmain函数.如果系统成功创建了一个新进程和主线程,CreateProcess将返回TRUE(在进程完全初始化好之前就返回TRUE,即意味着操作系统加载程序尚未尝试定位所有必要的DLL).
参数详解:如果一个命令行包含在文件映像的只读部分,就会引起访问为违例.
■ pszApplicationName和pszCommandLine参数:否则供函数用于创建新进程,当函数解析参数时,就会检测字符串中的第一个标记(token),并假设此标记是我们想要运行的可执行文件的名称.如果可执行文件的名称没有扩展名,就默认.exe为拓展名.此外函数的搜索顺序是:
1).主调进程.exe文件所在的目录
2).主调进程的当前目录.
3).window系统目录,即GetSystemDirectory
4).window目录.
5).PATH环境变量中所列出的目录
■ psaProcess,psaThread或bInheritHandles:继承关系想要看bInheritHandles参数,如果其为TRUE,则考虑psaProcess的进程属性,psaThread线程的属性..否则无需考虑,一律不继承.
■ fdwCreate 取值条件:
1). DEBUG_PROCESS:标志向系统表明父进程希望调式子进程以及子进程将来生成的所有进程.该标志向系统表明,在任何一个子进程(现在的身份是被调式的进程)中发生特定的事件时,要通知父进程(现在的身份是调试者)
2).DEBUG_ONLY_THIS_PROCESS:类似于DEBUG_PROCESS,但是,只有在关系最近的子进程中发生特定事件时,父进程才会得到通知.
3).CREATE_SUSPENDED:系统在创建新程的同时挂起其主线程.(可以使用ResumeThread启动)
4).DETACHED_PROCESS:阻止一个基于CUI的进程访问其父进程的控制台窗口,并告诉系统将它的输出发送到一个新的控制台窗口.如果一个基于CUI进程是由另一个基于CUI的进程创建的,那么默认情况下,新进程将使用父进程的控制台窗口.
5).CREATE_NEW_CONSOLE:指示系统为新进程创建一个新的控制台窗口(不能与上一标志同时使用)
6).CREATE_NO_WINDOW:指示系统不要为应用程序创建任何控制台窗口.
7).CREATE_NEW_PROCESS_GROUP:修改用户按Ctrl+C或Ctrl+Break时获取通知的进程列表.(按下这些组合键时,假如有多个CUI进程正在运行,系统将通知一个进程组中的所有进程,开始中断操作.
8).CREATE_DEFAULT_ERROR_MODE标志向系统表明新进程不会继承父进程所用的错误模式.
9).CREATE_SEPARATE_WOW_VDM:只在16位有用.
10).CREATE_SHARED_WOW_VDM:同上
11).CREATE_UNICODE_ENVIRONMENT:告诉系统子进程的环境块应该包含Unicode字符(默认是ANSI)
12).CREATE_FORCEDOS强制系统运行一个嵌入在16位os/2应用程序中的MS-DOS程序
13).CREATE_BREAKWAY_FROM_JOB:允许一个作业中的进程生成一个和作业无关的进程.
14).EXTENDED_STARTUPINFO_PRESENT:向系统表明传给psiStartInfo是STARTUPINFOEX结构
■ pvEnvironment参数:指向包含新进程要使用的环境字符串.NULL表明子进程继承其父进程使用的一组环境字符串.(可以使用GetEnvironmentStirngs();来获取.使用FreeEnvironmentStrings来释放.
■ pszCurDir:允许父进程设置子进程的当前驱动器和目录.如果为NULL,则新进程的工作目录将与生成新进程的应用程序一样.
■ psiStartInfo:
typedef struct _STARTUPINFO
{
DWORD cb;//包含结构体的字节数
PSTR lpReserved;//保留值,必须初始化为0
PSTR lpDesktop;//标识一个名称,表明要在哪个桌面上启动应用程序.(为空表示进程与当前桌面相连).
PSTR lpTitle;//控制台窗口的标题(为NULL表明将执行文件的名称作为标题)
DWORD dwX;//指定应用程序窗口在屏幕上的位置(即xy坐标)
DWORD dwY;
DWORD dwXSize;//指定应用程序窗口的宽度和高度
DWORD dwYSize;
DWORD dwXCountChars;//指定资金分成的控制台窗口的高度和宽度
DWORD dwYCountChars;
DWORD dwFillAttribute;//指定了子进程的控制台窗口所用的文本和背景色
DWORD dwFlags;
WORD wShowWindow;//指定应用程序的主窗口如何显示
WORD cbReserved2;//保留
PBYTE lpReserved2;//保留
HANDLE hStdInput;指定到控制台输出输入缓冲区的句柄.
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO,*LPSTARTUPINFO;
dwFlags:包含一组标志.取值与含义如下:
标志 | 含义 |
STARTF_USRSIZE | 使用dwXSize和dwYsize成员 |
STARTF_USESHOWWINDOW | 使用wShowWindow成员 |
STARTF_USEPOSITION | 使用dwX和dwY |
STARTF_USECOUNTCHARS | 使用dwXCountChars和dwYCountChars |
STARTF_USEFILLATTRIBUTES | 使用FillAttribute |
STARTF_USESTDHANDLE | 使用hStdInput,hStdOutput和hStdError |
STARTF_RUNFULLSCREEN | 使x86计算机上运行的一个控制台应用程序以全屏模式启动 |
另外还有两个标志:即STARTF_FORCEONFEEDBACK和STARTF_FORCEOFFFEEDBACK,可以利用他们控制驱动新进程时控制鼠标指针
//一般情况下都希望生成的应用程序只是使用默认值(起码要将此结构中的所有成员初始化为0,cb则设置为此结构的大小)
//但是如果忘记把结构清零,这成员则包含主调线程栈上的垃圾数据.而正是由于这些垃圾数据,使得CreateProcess创建的线程有时失效
typedef struct _STARTUPINFOEX
{
STARTUPINFO StartupInfo;
struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttrobuteList;
};
lpAttrobuteList仅有两个属性值被写入文档:
◆ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,这个属性告诉CreateProcess子进程究竟应该继承哪一些内核对象内核句柄。
◆ PROC_THREAD_ATTRIBUTE_PARENT_PROCESS:这个属性键的预期值是一个进程句柄。
■ ppiProcInfo参数:指向一个PROCESS_INFORMATION结构。
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess,//返回新建子进程的句柄
HANDLE hThread,//新建子线程的句柄
DWORD dwProcessId,//子进程ID
DWORD dwThreadId;//子线程ID
}PROCESS_INFORMATION;
子进程创建成功后,将会填充这个结构.把相应的值填入成员变量中.但是要注意的一点是,在父进程中如果使用这个进程ID和句柄.必须确保子进程没有退出才行.(也正是这个原因,当我们启动应用软件的卸载程序,他一般会立即启动另外一个子进程.而该进程会立即退出.所以,父进程返回的ID可能已经重用了.这样就造成一些错误).
10. 终止进程.
进程可以通过以下4种方式终止:
◆ 主线程的入口点函数返回
◆ 进程中的一个线程调用ExitProcess函数(要尽量避免这种方式)
◆ 另一个进程中的线程调用TerminateProcess函数(要避免使用这种方式)
◆ 进程中的所有线程都自然死亡(这种情况几乎不会发生)
Void ExitProcess(UINT fuExitCode);该函数终止线程,并将进程的退出码设为fuExitCode.
BOOL TerminateProcess(HANDLE hProcess,
UINT fuExitCode)//任何线程都可以利用该函数来终止另一个进程或者自己的进程
注意:TerminateProcess是异步的.
进程终止时执行的操作有:
1). 终止进程中遗留的任何线程
2). 释放进程分配的所有用户对象和GDI对象,关闭所有内核对象(如果没有其他进程打开这些内核对象的句柄,那么他们也会被销毁.否则,就不销毁)
3). 进程退出码从STILL_ACTIVE变为ExitProcess或TerminateProcess函数的代码.
4). 进程内核对象的状态变成已触发状态
5). 进程内核对象的使用计数递减1.
BOOL GetExitCodeProcess(HANDLE hProcess,
PDWORD pdwExitCode);获取已经终止的一个进程的退出代码
如果进程还没终止,将用STILL_ACTIVE标识符来填充pdwExitCode.如果已经终止,则返回实际退出代码值.
11. ShellExecute详解:
ShellExecute(HWND hwnd,//父窗口句柄
LPCSTR lpOperation,//操作类型
LPCSTR lpFile,//要进行操作的文件或路径
LPCSTR lpParameters,//当lpOperation为"explore"时,指定要传递的参数,通常设为NULL
LPCSTR lpDirectory,//指定默认目录,通常设为NULL
INT nShowCmd)//文件打开方式,以通常方式还是最大化或最小化显示
例子:1).调用计算器:
ShellExecute(NULL,"open","calc.exe",NULL,NULL,SW_SHOWNORMAL)
2).调用记事本
ShellExecute(NULL,"open","NOTEPAD.exe",NULL,NULL,SW_SHOWNORMAL);
参数:hwnd:但函数调用过程出现错误时,它将作为Windows消息窗口的父窗口.
Operation:用于指定要进行的操作.其中"open"表示执行由FileName尝试指定的程序,打开由FileName尝试指定的文件或文件夹."print"表示打印有FileName尝试指定的文件.
"explore"操作表示浏览有FileName尝试指定的文件夹.当参数为NULL时,表示执行默认操作"open".
FileName用于指定要打开的文件名,要执行的文件名或要浏览的文件夹名.
Parameters:若FileName参数是一个可执行程序,这此参数指定命令行参数.
Directory:用于指定默认目录
ShowCmd:若FileName参数是一个可执行程序,则此参数指定程序窗口的初始显示方式,否则此参数应设置为0.
返回值:成功,返回被指定程序的实例句柄.
打开网页:
ShellExecute( m_hWnd,"open","http://www.baidu.com",NULL,NULL,SW_SHOWNORMAL );
12. 提升进程权限:
BOOL ShellExecuteEx(LPSHELLEXECUTEINFO pExecInfo);
Typedef struct _SHELLEXECUTEINFO{
DWORD cbSize;
ULONG fMask;
HWND hwnd;
PCTSTR lpVerb;//必须为"runas"
PCTSTR lpFile;//包含使用提升后的权限来启动一个可执行文件的路径
PCTSTR lpParameters;
PCTSTR lpDirectory;
Int nShow;
HINSTANCE hInstApp;
PVOID lpIDList;
PCTSTR lpClass;
HKEY hKeyClass;
DWORD dwHotKey;
Union{
HANDLE hIcon,
HANDLE hMonitor;
}
HANDLE hProcess;
}SHELLEXECUTEINFO,*LPSHELLEXECUTEINFO;