win32学习笔记(二)


前言

win32学习笔记的第二节,主要关注进程的操作原理和一些API函数的用法。

应用程序的启动过程

应用程序需要有一个入口函数,例如:控制台应用程序的入口函数为main。

但其实操作系统并不是真的去调用main函数,而是去调用C/C++运行期启动函数。此函数会初始化C/C++运行期库,它也会保证在用户的代码执行之前所有全局的或静态的C++对象能够被正确地创建。

对win32程序来说,程序的启动过程就是进程的创建过程,操作系统通过调用CreateProcess函数来创建新的进程。
同时,会有一个内核对象被创建,该内核对象并不是进程本身,而是系统用来管理进程的小的数据结构。

系统接着会为新线程创建一个主线程,主线程执行C/C++运行期启动代码,C/C++运行期启动代码又会调用main函数,如果系统能够成功创建新的进程和进程的主线程,CreateProcess函数会返回TRUE,否则返回FALSE。

进程创建——CreateProcess函数

创建进程被称为父进程,被创建的进程称为子进程。系统在创建新的进程时会为其指定一个STARTUPINFO类型的变量。其中包含了新进程传递给子进程的一些显示信息。
结构定义如下:

typedef struct _STARTUPINFO
{
DWORD cb; //包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,它可用作版本控制手段.应用程序必须将cb初始化为sizeof(STARTUPINFO)
LPSTR lpReserved; //保留。必须初始化为NULL
LPSTR lpDesktop; //用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。如果lpDesktop是NULL(这是最常见的情况 ),那么该进程将与当前桌面相关联
LPSTR lpTitle; //用于设定控制台窗口的名称。如果lpTitle是NULL,则可执行文件的名字将用作窗口名.This parameter must be NULL for GUI or console processes that do not create a new console window.
DWORD dwX; //用于设定应用程序窗口相对屏幕左上角位置的x 坐标(以像素为单位)。
DWORD dwY; //对于GUI processes用CW_USEDEFAULT作为CreateWindow的x、y参数,创建它的第一个重叠窗口。若是创建控制台窗口的应用程序,这些成员用于指明相对控制台窗口的左上角的位置
DWORD dwXSize; //用于设定应用程序窗口的宽度(以像素为单位)
DWORD dwYSize; //子进程将CW_USEDEFAULT 用作CreateWindow 的nWidth、nHeight参数来创建它的第一个重叠窗口。若是创建控制台窗口的应用程序,这些成员将用于指明控制台窗口的宽度
DWORD dwXCountChars; //用于设定子应用程序的控制台窗口的宽度(屏幕显示的字节列)和高度(字节行)(以字符为单位)
DWORD dwYCountChars;
DWORD dwFillAttribute; //用于设定子应用程序的控制台窗口使用的文本和背景颜色
DWORD dwFlags; //请参见下一段和表4-7 的说明
WORD wShowWindow; //用于设定如果子应用程序初次调用的ShowWindow 将SW_*作为nCmdShow 参数传递时,该应用程序的第一个重叠窗口应该如何出现。本成员可以是通常用于ShowWindow 函数的任何一个SW_*标识符,除了SW_SHOWDEFAULT.
WORD cbReserved2; //保留。必须被初始化为0
LPBYTE lpReserved2; //保留。必须被初始化为NULL
HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,hStdInput 用于标识键盘缓存,hStdOutput 和hStdError用于标识控制台窗口的缓存
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

不必掌握每个字段的具体意思,主要要了解创建进程的流程。
一个进程也可以调用GetStartupInfo函数来取得父进程创建自己时的STARTUPINFO结构。函数定义如下:

VOID GetStartupInfo(LPSTARTUPINFO lpstartupInfo);

定义了STARTUPINFO后必须对其中的cb成员进行初始化,因为不同版本的结构体可能发生变化,又要兼容以前的版本,Windows要通过结构体的大小来确定其成员的数目。

STARTUPINFO si={sizeof(si)};//初始化
::GetStartupInfo(&si);//取得STARTUPINFO

CreateProcess函数创建进程和主线程,新的进程会在父进程的安全上下文中运行可执行文件。

BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATIONlpProcessInformation
);

其中最重要的参数为lpApplicationName和lpCommandLine。

lpApplicationName参数一般为NULL,可以在此参数中指定可执行文件的文件名,且必须指定后缀。如果其中不包含目录,会假定和当前的调用进程在同一目录。所以最常用的方法是将其设为NULL。

lpCommandLine参数则为新进程制定了一个完整的命令行,如果命令行的第一个参数(文件名)没有后缀,会自动补上.exe。而且,CreateProcess函数会搜索该可执行文件。搜索路径的顺序为:
1)调用进程的可执行文件所在目录
2)当前目录
3)Windows的系统目录
4)Windows目录
5)在名称为PATH的环境变量中列出的目录

倒数第二个参数为刚才讲到的STARTUPINFO的指针类型,最后一个参数lpProcessInformation是指向PROCESS_INFORMATION结构的指针。其定义如下:

typedef struct_PROCESS_INFORMATION{
HANDLE hProcess;//返回新进程的句柄
HANDLE hThread;//返回主线程的句柄
DWORD dwProcessId;//返回一个全局进程标识符。该标识符用于标识一个进程。从进程被创建到终止,该值始终有效。
DWORD dwThreadId;//返回一个全局线程标识符。该标识符用于标识一个线程。从线程被创建到终止,该值始终有效。
}PROCESS_INFORMATION;

注意:进程和线程在创建后都会被分配ID号,但是这些ID号会被重复使用,而且进程和线程共用一个号码分配器。

下面是一个创建进程的实例:

#include<Windows.h>
#include<iostream>
#include<WINDEF.h>
int main(int argc, char** argv)
{
	STARTUPINFO si = { sizeof(si) };//初始化
	char szCommandLine[] ="cmd" ;//设置命令行参数
	PROCESS_INFORMATION pi;
	si.dwFlags = STARTF_USESHOWWINDOW;//令wShowWindow有效
	si.wShowWindow = TRUE;
	BOOL bRet = ::CreateProcess(
		NULL,
		szCommandLine,
		NULL,
		NULL,
		FALSE,
		CREATE_NEW_CONSOLE,//创建一个新的控制台
		NULL, 
		NULL,
		&si,
		&pi);
	if (bRet)
	{
		//在CreateProcess函数内部,会将进程和线程的对象句柄打开,传入PROCESS_INFORMATION,最终使得使用计数为2,所以要利用CloseHandle函数关闭内核对象的句柄
		::CloseHandle(pi.hThread);
		::CloseHandle(pi.hProcess);
		printf("进程ID :%d \n", pi.dwProcessId);
		printf("线程ID: %d \n", pi.dwThreadId);
		
	}
	return 0;
}

获取系统进程

使用ToolHelp函数可以列出当前正在运行的一系列进程。
CreateToolHelp32Snapshot函数用于获取系统内指定进程的快照,也可以获取被这些进程使用的堆、模块和线程的快照。函数原型:

HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);

函数执行成功将返回一个快照句柄,否则返回INVALID_HANDLE_VALUE(即-1)。
之后可以使用Processes2First和ProcessNext32来对快照列表进行遍历输出。
函数原型如下:

BOOL WINAPI Process32First(
  HANDLE hSnapshot,
  LPPROCESSENTRY32 &pe32
);
BOOL WINAPI Process32Next(
  HANDLE hSnapshot,
  LPPROCESSENTRY32 &pe32
);

第一个参数为快照句柄,第二个参数为PROCESSENTRY32结构体指针,其中保存了进程的信息。PROCESSENTRY32定义如下:

typedef struct tagPROCESSENTRY32
{
    DWORD dwSize;//结构体长度
    DWORD cntUsage;//进程的引用次数
    DWORD th32ProcessID;//进程ID
    ULONG_PTR th32DefaultHeapID;//进程默认堆的ID
    DWORD th32ModuleID;//进程模块的ID
    DWORD cntThreads;//进程创建的线程数
    DWORD th32ParentProcessID;//进程的父线程ID
    LONG pcPriClassBase;//进程创建的线程的基本优先级
    DWORD dwFlags;//内部使用
    TCHAR szExeFile[MAX_PATH];//进程对应的可执行文件名
} PROCESSENTRY32, *PPROCESSENTRY32;

获取进程列表的实例如下:

#include<Windows.h>
#include<iostream>
#include<WINDEF.h>
#include<tlhelp32.h>
int main(int argc, char** argv)
{
	PROCESSENTRY32 pe32;
	pe32.dwSize=sizeof(pe32);//同样要初始化大小
	
	HANDLE hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
	if(hProcessSnap==INVALID_HANDLE_VALUE)
	{
		printf("CreateToolhelp32Snapshot fail\n");
		return-1;
	}
	BOOL bMore=::Process32First(hProcessSnap,&pe32);
	while(bMore)
	{
		printf("进程名称 %s\n",pe32.szExeFile);
		printf("进程ID %u \n\n",pe32.th32ProcessID);
		bMore=::Process32Next(hProcessSnap,&pe32);
	}
	::CloseHandle(hProcessSnap);
	return 0;
}

结果:

终止当前进程

进程终止的原因:
1)主线程的入口函数返回
2)进程中有一个线程调用了ExitProcess函数
3)此进程中的所有线程结束了
4)其他进程中的一个线程调用了TerminateProcess函数
结束当前的进程一般是让主线程的入口函数返回,当用户的入口函数返回时,启动函数会调用C/C++运行期退出函数exit,并将用户的返回值传递给它。exit函数会销毁所有全局或者静态的c++对象,然后调用系统函数ExitProcess使程序终止。一般不直接在程序中使用ExitProcess函数,这样会使C/C++运行期库得不到通知,而没有机会去调用全局的或静态的C++对象的析构函数。
ExitProcess函数原型如下:

void ExitProcess(UINT uExitCode);//参数为此程序的推出代码

终止其他进程

结束其他进程可以使用TerminateProcess函数

BOOL TerminateProcess(
	HANDLE hProcess,//要结束的进程的句柄
	UINT uExitCode//退出码,可以使用GetExitCodeProcess获取一个进程的退出码
	);

使用OpenProcess函数来取得进程的访问权限,原型如下:

HANDLE OpenProcess(
DWORD dwDesiredAccess,//想得到访问权限,可以是PROCESS_ALL_ACCESS
BOOL bInheritHandle,//指定返回的句柄是否可以被继承
DWORD dwProcessId//指定要打开的进程的ID号
);

实例:

BOOL TerminateProcessFromId(DWORD dwId)
{
BOOL bRet=FALSE;
//打开目标进程,取得句柄
HANDLE hProcess=::OpenProcess(PROCESS_ALL_ACESS,FALSE,dwId);
if(hProcess !=NULL)
{
bRet=:TerminateProcess(hProcess,0);
}
CloseHandle(hProcess);
return bRet;
}

进程终止的后果:
1)所有被这个进程创建或打开的对象句柄就会关闭
2)此进程内的所有线程将终止执行
3)进程内核对象变成受信状态,所有等待在此对象上的线程开始运行,即WaitForSingObject函数返回
4)系统将进程对象中退出代码的值由STILL_ACTIVE改为指定的退出码

总结

本节讲解了进程的创建和控制,包括了如何创建进程、如何终止进程和获取进程快照列表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值