1、进程相关
进程是一个正在运行的程序的实例。进程从来不执行任何东西,它只是线程的容器,或者说是线程的执行环境,真正完成代码执行的是线程。每个进程至少有一个线程,可以把main函数作为主线程的入口。
进程由两部分组成:一是操作系统用来管理进程的内核对象;一个是地址空间:其中包含可执行模块或DLL的代码和数据,也包含动态内存分配的空间,例如线程的栈和堆分配的空间。
对于32位进程,系统为每个进程分配4G的独立虚拟地址空间,因为对32位指针来说,它能寻址的范围最大是2的32次方,即4GB。虚拟地址空间只是内存地址的一个范围,在你能成功的访问数据而不出现非法操作之前,必须赋予物理存储器。其中物理存储器包括内存和页文件。页文件(pagefile,简称PF)通过在磁盘上划分出一块空间作为内存使用,从而增加了程序可以使用的内存,这块内存称作虚拟内存。
2、内核对象
内核对象供系统来管理进程、线程、文件等,CreateThread()、CreateFile()、CreateFileMapping()、CreateEvent()等都会创建一个相关的内核对象,并返回这个内核对象的句柄,调用CloseHandle()用来关闭这个内核对象。
在很多情况下,需要跨越进程共享内核对象:
子进程可以继承父进程中可继承的打开句柄,这种方式可以实现父子进程间的内核对象共享。
实现不同进程共享内核对象的第二种方法是给内核对象命名,例如CreateMutex()、CreateEvent()、CreateSemaphore()时创建命名的对象。如果创建的是匿名对象,那么可以通过使用继承性或DuplicateHandle()来共享跨越进程的对象。
第三种实现跨进程共享内核对象的方法为使用DuplicateHandle()函数,DuplicateHandle()函数复制一个进程的对象句柄到另一个进程的句柄表中。
3、环境变量
每个进程都有一个与它相关的环境块,环境块中包含环境变量名及其值,eg:
VarName1=Var1Value\0
VarName2=Var2Value\0
VarName3=Var3Value\0
.......
GetEnvironmentStrings()可以获得调用进程的环境块的地址,当不使用该内存块时应调用FreeEnvironmentStrings()释放内存块。
可以使用GetEnvironmentVariable 函数来判断一个环境变量是否存在;如果存在,它的值是什么。
可以使用ExpandEnvironmentStrings函数来展开一个环境变量 ,如: 在我的机器上USERPROFILE环境变量值为C:\Users\mzf,因此 %USERPROFILE%\Documents 展开后为:C:\Users\mzf\Documents。
可以使用SetEnvironmentVariable函数添加一个环境变量,删除一个环境变量,或者修改一个环境变量的值。
4、继承性
子进程继承一组与父进程相同的环境变量,即子进程获得父进程环境块的拷贝,子进程与父进程并不共享相同的环境块。
子进程会继承父进程的当前目录。
可以通过设置CreateProcess()的bInheritHandles参数,来决定子进程是否继承父进程中可继承的打开句柄。
5、创建进程
CreateProcess()用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。
BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation );
lpApplicationName:可执行文件名,这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数最前面
并由空格符与后面的字符分开,如果可执行模块名中有空格则应该使用引号将该名称包含起来,如果可指定文件没有指定扩展名则函数会为其
自动添加.exe。
lpCommandLine:命令行字符串,注意为LPTSTR类型,故不能传常量字符串。
lpProcessAttributes(psaProcess):
lpThreadAttributes(psaThread):
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;//结构体大小
LPVOID lpSecurityDescriptor;//安全描述符,NULL则为使用默认的安全描述符
BOOL bInheritHandle;//继承关系
} SECURITY_ATTRIBUTES;
在这个SECURITY_ATTRIBUTES结构体中还可以指定新进程的进程句柄和线程句柄是否可以被父进程以后创建的进程所继承,如果想要实现新进程的进程句柄和线程句柄都可以被父进程以后创建的进程所继承,那么psaProcess.bInheritHandle和lpThreadAttributes.bInheritHandle都应该设为TRUE,同时CreateProcess()的bInheritHandle也应该设为TRUE。
bInheritHandles:如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承,被继承的句柄与原进程拥有完全相同的值和访问权限。
dwCreationFlags:指定附加的、用来控制优先类和进程的创建的标志,如新进程的主线程会以暂停的状态被创建(CREATE_SUSPENDED),直到调用ResumeThread函数被调用时才运行、 新的进程不继承调用进程的错误模式、系统不为新进程创建CUI窗口(创建不含窗口的CUI程序)、修改用户在按下Ctrl+C或Ctrl+Break时得到通知的进程组、 还可以控制新进程的优先类,优先类用来决定此进程的线程调度的优先级,如果下面的优先级类标志都没有被指定,那么默认的优先类是NORMAL_PRIORITY_CLASS: 优先级:IDLE_PRIORITY_CLASS 含义:空闲。指示这个进程的线程只有在系统空闲时才会运行并且可以被任何高优先级的任务打断。例如屏幕保护程序。空闲优先级会被子进程继承。 优先级:BELOW_NORMAL_PRIORITY_CLASS 含义:低于正常。 优先级:NORMAL_PRIORITY_CLASS 含义:正常。指示这个进程没有特殊的任务调度要求。优先级:REALTIME_PRIORITY_CLASS 含义:实时。指示这个进程拥有可用的最高优先级。一个拥有实时优先级的进程的线程可以打断所有其他进程线程的执行,包括正在执行重要任务的系统进程。 例如,一个执行时间稍长一点的实时进程可能导致鼠标反映迟钝或磁盘缓存不足。优先级:ABOVE_NORMAL_PRIORITY_CLASS 含义:高于正常。优先级:HIGH_PRIORITY_CLASS 含义:高。指示这个进程将执行时间临界的任务,所以它必须被立即运行以保证正确。这个优先级的程序优先于正常优先级或空闲优先级的程序。一个例子是 Windows任务列表,为了保证当用户调用时可以立刻响应,放弃了对系统负荷的考虑。确保在使用高优先级时应该足够谨慎,因为一个高优先级的CPU 关联应用程序可以占用几乎全部的CPU可用时间。
lpEnvironment:指向新进程的环境块。若为NULL则为继承父进程的环境块,此时如同传入GetEnvironmentStrings()的返回值一样。
lpCurrentDirectory:指向新进程的当前目录,如果这个参数为NULL,新进程将使用与调用进程相同的驱动器和目录。这个选项是一个需要启动应用程序并指定它们的
驱动器和工作目录的外壳程序的主要条件。
lpStartupInfo:指向一个STARTUPINFO结构。
lpProcessInformation:指向一个PROCESS_INFORMATION,用来接收新进程的相关信息,该结构形式如下:
WinExec()和LoadModule()函数依旧可用,但是它们同样通过调用CreateProcess函数实现。
调用进程可以通过WaitForInputIdle()函数来等待新进程完成它的初始化并等待用户输入。这对于父进程和子进程之间的同步是极其有用的,因为CreateProcess()函数不会等待新进程完成它的初始化工作。举例来说,在试图与新进程关联的窗口通讯之前,进程应该先调用WaitForInputIdle()。
以下代码为执行IE程序并打开指定的网页:
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = TRUE; //TRUE表示显示创建的进程的窗口
TCHAR cmdline[] =_T("c://program files//internet explorer//iexplore.exe http://www.baidu.com/");
BOOL bRet = ::CreateProcess (
NULL,
cmdline, //在Unicode版本中此参数不能为常量字符串,因为此参数会被修改
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
GetProcessId()可以获得指定进程的ID。
GetCurrentProcessId()可以获得当前进程ID。
OpenProcess()可以获得指定进程的句柄,在使用完所获得的进程句柄后一定要调用CloseHandle(handle)使进程内核对象的使用计数减1。
GetCurrentProcess()可以获得当前进程的伪句柄。
6、关闭进程
7、子进程
如果想创建新进程,并等待子进程运行结束,可以使用类似以下的代码:
PROCESS_INFORMATION ProInfo;
DWORD dwExitCode;
BOOL fSuccess = CreateProcess(......, &ProInfo);
if(fSuccess)
{
CloseHandle(ProInfo.hThread);
WaitForSingleObject(ProInfo.hProcess, INFINITE);
GetExitCodeProcess(ProInfo.hProcess, &dwExitCode);
CloseHandle(ProInfo.hProcess);
}
WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)函数用来检测参数hHandle事件的信号状态。在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
GetExitCodeProcess()获取一个已中断进程的退出代码。
CloseHandle(HANDLE)用来关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄。调用CloseHandle表示创建者放弃对该内核对象的拥有和操作。函数成功,把内核对象中的使用计数成员减1,如果计数变为0,则将从内核中释放该内核对象,如果计数还未到0,就意味着还有其他的进程在使用这个内核对象,那么它就不会被释放。在线程创建后马上调用CloseHandle()是个良好的做法,如果你不需要对它进行干预。在这里,不必担心CloseHandle()后其引用计数变为0,因为CreateThread后那个线程的引用计数不是1,而是2,线程作为一种资源创建后不只被创建线程引用,我想系统自身为了管理线程也会有一个引用,所以用户线程释放线程句柄后,引用计数也不会是零。
如果要子进程独立运行,不再等待其运行结束,则应调用CloseHandle()来关闭与新进程及其主线程之间的句柄:
PROCESS_INFORMATION ProInfo;
BOOL fSuccess = CreateProcess(......, &ProInfo);
if(fSuccess)
{
CloseHandle(ProInfo.hThread);
CloseHandle(ProInfo.hProcess);
}
8、exe程序操作
//获得当前exe所在目录
CString GetExeDirectory(void)
{
CString strFilePath = L"";
if (!GetModuleFileName(NULL, strFilePath.GetBufferSetLength(MAX_PATH + 1), MAX_PATH))
{
return CString("");
}
int n = strFilePath.ReverseFind('\\');
strFilePath = strFilePath.Left(n);
return strFilePath;
}
//打开exe或网页
BOOL OpenExe(CString strFile)
{
SHELLEXECUTEINFO info;
memset(&info, 0, sizeof(info));
info.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;//出错不显示错误信息, hProcess获得新程序的进程句柄
info.lpFile = strFile;//程序名或要打开的网页地址
//info.lpParameters = _T("CheckOK");//参数
info.lpDirectory = GetExeDirectory();//工作目录
info.nShow = SW_SHOW;
//info.lpVerb = _T("runas");//管理员身份运行
info.cbSize = sizeof(info);
BOOL bResult = ShellExecuteEx(&info);
//WaitForSingleObject(info.hProcess, INFINITE);//等待程序运行结束
return bResult;
}
以下为获得当前系统版本函数,使用需要包含头文件<VersionHelpers.h>:
IsWindowsXPOrGreater()
IsWindowsVistaOrGreater()
IsWindows7OrGreater()
IsWindows8OrGreater()
IsWindows10OrGreater()
IsWindowsServer()
来源:《孙鑫VC++深入详解》、《Windows核心编程》