win32api之进程的创建与使用(二)
创始人
2025-05-30 21:21:00
0

什么是进程

在计算机操作系统中,进程是正在运行中的程序的实例。进程是操作系统进行资源分配和管理的基本单位,包括内存、文件句柄、系统状态等。每个进程都有自己的独立内存空间和运行状态,因此它们不会互相干扰,也不会互相影响。多个进程可以在操作系统上同时运行,每个进程都在自己的空间里执行自己的代码


进程内存空间的划分

在Windows X86环境下, 进程的内存空间通常被划分为以下三个区域

分区描述地址范围
用户模式区用户模式区占据了进程地址空间的大部分,通常是4GB大小。它包含了程序代码、堆、栈、数据等,是进程中大部分数据的存放位置0x00000000 ~ 0x7FFFFFFF (2GB)
系统保留区由于内核区和用户模式区之间存在一定的限制和隔离,这个区域可以用来共享一些必要的数据,以便内核和用户模式的进程之间能够进行通信和数据共享0x7FFE0000 - 0x7FFFFFFF (128KB)
内核区内核模式区只占据了进程地址空间的一小部分,它通常包含了内核数据结构、操作系统代码和其他内核相关的资源。内核模式区对于应用程序来说是不可访问的,只能通过系统调用等方式来访问其中的数据和功能0x80000000 ~ 0xFFFFFFFF (2GB)

进程的创建过程

首先有一点要清楚, 任何进程都是别的进程创建的, 而第一个进程是系统启动时由操作系统内核创建的, 它的进程ID为0, 称为系统空闲进程或系统进程

在Windows中,进程的创建可以使用CreateProcess函数,该函数会返回一个指向新进程的句柄

1.为进程分配内存空间,加载EXE文件, 并将其映射到内存

image-20230216194610827


2.创建进程内核对象EPROCESS

EPROCESS是Windows操作系统内核中的一种数据结构,它代表了一个进程对象。EPROCESS结构体包含了进程的许多信息,例如进程的PID、进程的线程列表、虚拟内存映射、访问权限、I/O访问权限等等

image-20230216194949260


3.映射系统dll(ntdll.dll)至内存

image-20230216195410482


4.创建线程内核对象ETHREAD

创建进程的时候系统会默认创建一个线程, 也就是说每个进程都至少有一个线程

image-20230216195620381


5.系统启动线程

线程启动之前, 还需要映射运行可执行文件所需的其他dll文件, 随后线程才开始执行

image-20230216200411778


进程涉及API

CreateProcess

CreateProcess函数用于创建一个新的进程并返回进程句柄, 其原型如下

BOOL CreateProcess(LPCTSTR lpApplicationName,  //需要运行的可执行文件名LPTSTR lpCommandLine,  //命令行参数字符串LPSECURITY_ATTRIBUTES lpProcessAttributes,  //进程的安全属性LPSECURITY_ATTRIBUTES lpThreadAttributes,  //线程的安全属性BOOL bInheritHandles,  //是否继承父进程的句柄DWORD dwCreationFlags,  //进程标志 LPVOID lpEnvironment,  //新进程的环境变量,如果为NULL,则将使用当前进程的环境变量LPCTSTR lpCurrentDirectory,  //新进程的当前目录LPSTARTUPINFO lpStartupInfo,  //指向STARTUPINFO结构的指针,包含了启动进程时的窗口状态和标志等信息LPPROCESS_INFORMATION lpProcessInformation  //指向PROCESS_INFORMATION结构的指针,用于返回新进程的进程句柄和主线程句柄
);

如下是PROCESS_INFORMATION结构的成员

typedef struct _PROCESS_INFORMATION {HANDLE hProcess;  //进程句柄	HANDLE hThread;  //线程句柄DWORD dwProcessId;  //进程IDDWORD dwThreadId;  //线程ID
}

如下是STARTUPINFO结构的成员

typedef struct _STARTUPINFOW {DWORD   cb;   //结构体大小LPWSTR  lpReserved;  //保留,置为NULLLPWSTR  lpDesktop;  //指定进程的窗口站和桌面名称,或者是一个空字符LPWSTR  lpTitle; //指定进程的窗口标题DWORD   dwX;  //指定主窗口左上角的初始位置,以屏幕坐标表示DWORD   dwY;  DWORD   dwXSize;  //指定主窗口的宽度和高度,以像素表示DWORD   dwYSize;  DWORD   dwXCountChars;   //指定主窗口的字符宽度和字符高度DWORD   dwYCountChars;DWORD   dwFillAttribute;  //指定用于填充主窗口的初始颜色和属性DWORD   dwFlags;  //指定控制进程如何创建的一组标志WORD    wShowWindow;  //指定主窗口最初如何显示WORD    cbReserved2;  //保留成员LPBYTE  lpReserved2;  //保留成员HANDLE  hStdInput;  //标准输入设备的句柄HANDLE  hStdOutput;  //标准输出设备的句柄HANDLE  hStdError;  //标准错误设备的句柄
}

如下代码是一个创建进程的简单实例, 用于启动计算器(calc.exe)

include 
include //创建子进程函数,传递两个参数,分别是应用程序名字和程序命令行参数,若子进程创建成功则返回子进程句柄
DWORD CreateChildProcess(TCHAR ApplicationName[],TCHAR CommandLine[]=NULL) {STARTUPINFO si;  //进程启动信息PROCESS_INFORMATION pi;  //进程信息ZeroMemory(&si, sizeof(si));  //将结构体si的所有成员都初始化为0ZeroMemory(&pi, sizeof(pi));  //将结构体pi的所有成员都初始化为0si.cb = sizeof(si);  //结构体大小if (!CreateProcess(ApplicationName,  //要执行的应用程序名称(包含路径)NULL,  //命令行参数NULL,  //进程句柄不可被继承NULL,  //线程句柄不可被继承FALSE,  //不继承句柄0,  //标志位为0NULL,  //使用父进程的环境变量NULL,  //使用父进程的工作目录&si,  //传递启动信息&pi)  //传递进程信息) {printf("CreateProcess failed (%d).\n", GetLastError());  //打印错误信息return 0;}return (DWORD)pi.hProcess;  //返回进程句柄//return (DWORD)pi.dwProcessId;  //返回进程ID//释放进程句柄和线程句柄CloseHandle(pi.hProcess);CloseHandle(pi.hThread);
}int main()
{TCHAR ApplicationName[] = TEXT("E:\\calc.exe");DWORD ProcessHandle = CreateChildProcess(ApplicationName);return 0;
}

若需要以挂起的形式创建进程, 可将CreateProcess函数的第六个参数设置为CREARE_SUSPEND, 这样创建的进程一开始并不会自动启动线程, 而是需要自己手动执行ResumeThread函数恢复线程后才会启动

include 
include int main()
{STARTUPINFO si;  //进程启动信息PROCESS_INFORMATION pi;  //进程信息ZeroMemory(&si, sizeof(si));  //将结构体si的所有成员都初始化为0ZeroMemory(&pi, sizeof(pi));  //将结构体pi的所有成员都初始化为0si.cb = sizeof(si);  //结构体大小TCHAR ApplicationName[] = TEXT("E://test.exe");  //test.exe是一个只输出"进程执行"的文件if (!CreateProcess(ApplicationName,  //要执行的应用程序名称(包含路径)NULL,  //命令行参数NULL,  //进程句柄不可被继承NULL,  //线程句柄不可被继承FALSE,  //不继承句柄CREATE_SUSPENDED,  //以挂起的形式创建进程NULL,  //使用父进程的环境变量NULL,  //使用父进程的工作目录&si,  //传递启动信息&pi)  //传递进程信息) {printf("CreateProcess failed (%d).\n", GetLastError());  //打印错误信息return 0;}for (int i = 0; i < 5; i++){printf("#######\n");Sleep(1000);}ResumeThread(pi.hThread);//释放进程句柄和线程句柄CloseHandle(pi.hProcess);CloseHandle(pi.hThread);
}

如上代码所示, 线程会在for循环打印代码执行结束后才会启动线程, 执行结果如下

image-20230221085734005

OpeoProcess

OpenProcess函数用于打开一个已存在的进程对象,以便对该进程执行操作,例如向该进程发送信号或从该进程读取内存。此函数的调用者必须具有足够的权限来打开目标进程

如果函数执行成功,返回打开进程的句柄,否则返回NULL,并可通过调用GetLastError函数获取错误码

要注意的是, 使用CloseHandle函数释放句柄后, 就不能再使用OpenProcess函数来打开这个进程了, 因为CloseHandle函数会将句柄从进程的句柄表中移除,并且在所有引用计数都归零之后释放内存资源

OpenProcess函数的语法如下:

HANDLE OpenProcess(DWORD dwDesiredAccess,  // 指定进程的访问权限,可取值为PROCESS_ALL_ACCESS或其他指定的进程访问权限常量BOOL bInheritHandle,    // 指定句柄是否可被子进程继承,TRUE表示可继承,FALSE表示不可继承DWORD dwProcessId       // 指定要打开的进程的进程ID
);

以下是dwDesireAccess参数的可取值:

  • PROCESS_ALL_ACCESS:具有完全访问权限的进程访问权限。
  • PROCESS_CREATE_PROCESS:允许创建新进程。
  • PROCESS_CREATE_THREAD:允许在进程中创建新线程。
  • PROCESS_DUP_HANDLE:允许进程使用 DuplicateHandle 函数复制句柄。
  • PROCESS_QUERY_INFORMATION:允许查询进程信息,如进程ID、进程优先级等。
  • PROCESS_QUERY_LIMITED_INFORMATION:允许查询受限信息,如进程ID、进程优先级、进程占用内存等。
  • PROCESS_SET_INFORMATION:允许设置进程信息,如进程优先级、进程AffinityMask等。
  • PROCESS_SET_QUOTA:允许设置进程的工作集大小和默认的硬错误模式。
  • PROCESS_SUSPEND_RESUME:允许挂起和恢复进程。
  • PROCESS_TERMINATE:允许终止进程。
  • PROCESS_VM_OPERATION:允许进行虚拟内存操作,如 VirtualAlloc、VirtualProtect 等。
  • PROCESS_VM_READ:允许读取进程的虚拟内存。
  • PROCESS_VM_WRITE:允许写入进程的虚拟内存

TeminateProcess

TerminateProcess函数是Windows操作系统提供的函数之一,用于终止指定进程, 当调用TerminateProcess函数时,会向指定进程发送一个中断信号,强制其终止。

这个过程是非常暴力的,会直接终止进程的所有线程,不会给进程和线程任何清理资源的机会,因此使用该函数需要非常慎重

函数定义如下:

BOOL TerminateProcess(HANDLE hProcess,  //进程句柄,用于标识被终止的进程UINT   uExitCode  //进程的退出代码,表示进程退出的原因,可以随意填写
);

GetModuleFileName

GetModuleFileName函数用于获取指定模块的完整路径名。通常情况下,可以通过指定NULL作为参数hModule,来获取当前应用程序的完整路径名,该函数的声明如下:

DWORD GetModuleFileName(HMODULE hModule,  // 模块句柄,指定NULL表示获取当前应用程序的路径名LPTSTR lpFilename,  // 接收完整路径名的缓冲区DWORD nSize  // 缓冲区大小
);

GetCurrentDirectory

GetCurrentDirectory函数用于获取当前进程的工作目录。其函数原型为

DWORD GetCurrentDirectory(DWORD  nBufferLength,  // 缓冲区大小,单位为字节LPTSTR lpBuffer        // 存储路径的缓冲区
);

这个函数的路径是指当前进程的工作目录,而不是当前模块的目录。如果需要获取当前模块的目录,需要使用GetModuleFileName函数来获取模块文件的路径

include 
include int main()
{	//获取当前模块的完整路径char str1[256];GetModuleFileName(NULL, str1, 256);printf("当前应用程序路径名:%s\n", str1);//获取当前应用程序的工作目录char str2[256];GetCurrentDirectory(256, str2);printf("当前程序工作目录:%s", str2);return 0;
}

image-20230220162026966


GetStartupInfo

GetStartupInfo函数用于检索当前进程的启动信息,它的主要功能是获取STARTUPINFO结构体,其中包含了进程的启动信息,如命令行参数、标准输入输出句柄、窗口显示方式等

调用GetStartupInfo函数需传递一个指向STARTUPINFO类型的指针

int main()
{
// 定义一个 STARTUPINFO 结构体变量 si
STARTUPINFO si;// 使用 ZeroMemory 函数将 si 清零
ZeroMemory(&si, sizeof(si));// 设置 si 的 cb 字段
si.cb = sizeof(si);// 使用 GetStartupInfo 函数获取启动信息
GetStartupInfo(&si);// 输出当前进程窗口状态
printf("Show window command: %d\n", si.dwFlags);// 返回 0,表示程序执行成功
return 0;
}

GetCurrentProcessID

GetCurrentProcessID函数是Windows API中的一部分,它返回当前进程的进程ID(Process ID, 其返回值是一个无符号长整型

其语法格式如下:

GetProcessId(_In_ HANDLE Process
);

GetCurrentProcess

GetCurrentProcess函数是Windows API提供的一个函数,用于获取当前进程的句柄。该函数没有任何参数,调用后将返回一个类型为HANDLE的句柄,该句柄指向当前进程

GetCurrentProcess 获取当前进程的一个伪句柄GetCurrentProcess 总是返回-1(即0xFFFFFFFF),代表当前进程。这个句柄不在句柄表中,不是真正的句柄,所以叫伪句柄


EnumProcesses

EnumProcesses函数是Windows API中的一个函数,用于列举当前正在运行的进程的ID号,通常用于获取系统中所有进程的ID号列表。若函数执行成功则返回TRUE, 否则返回FLASE, 它的声明如下:

BOOL EnumProcesses(DWORD  *pProcessIds, // 接收进程ID的缓冲区DWORD  cb,           // 缓冲区大小(以字节为单位)DWORD  *pBytesReturned  // 实际写入缓冲区的字节数
);

如果包含了Windows.h头文件, 还提示"EnumProcesses"未定义标识符, 请检查是否正确链接了psapi.lib库文件。

可以通过在Visual Studio中转到“项目”菜单,然后选择“属性”来查看和配置链接器选项。在属性页面的左侧选择“链接器”,然后选择“输入”。在“附加依赖项”字段中添加“Psapi.lib”,然后包含头文件:include "psapi.h"

如下实例枚举当前系统运行的所有进程ID, 并打印至控制台

include 
include 
include "psapi.h"define ARRAY_SIZE 1024int main()
{DWORD aProcesses[ARRAY_SIZE], cbNeeded, cProcesses;if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)){printf("EnumProcesses failed: %d\n", GetLastError());return 1;}// 计算枚举到的进程数cProcesses = cbNeeded / sizeof(DWORD);// 打印进程IDfor (DWORD i = 0; i < cProcesses; i++){if (aProcesses[i] != 0){printf("Process ID: %u\n", aProcesses[i]);}}return 0;
}

image-20230223151412794


CreateToolhelp32Snapshot

CreateToolhelp32Snapshot函数是Windows系统提供的一个快照函数,可以获取系统中当前正在运行的进程和线程的快照。该函数可以通过枚举系统中所有进程和线程来帮助实现进程和线程的监控和管理。在调用该函数时,需要指定快照类型,如进程快照、线程快照等。函数会返回一个句柄,该句柄可以作为参数传递给其他Tool Help函数,以获取有关系统中进程和线程的详细信息。在使用完成后,需要调用CloseHandle函数关闭句柄

如下实例使用CreateToolhelp32Snapshot函数枚举系统所有进程:

include 
include 
include 
include int main()
{HANDLE hProcessSnap;PROCESSENTRY32 pe32;// 获取系统进程快照hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (hProcessSnap == INVALID_HANDLE_VALUE) {std::cout << "CreateToolhelp32Snapshot failed: " << GetLastError() << std::endl;return 1;}// 设置pe32结构体的大小,否则Process32First/Next函数会失败pe32.dwSize = sizeof(PROCESSENTRY32);// 获取第一个进程的信息if (!Process32First(hProcessSnap, &pe32)) {std::cout << "Process32First failed: " << GetLastError() << std::endl;CloseHandle(hProcessSnap);return 1;}// 遍历进程列表,输出每个进程的PID和名称do {_tprintf(TEXT("PID=%d, Name=%s\n"), pe32.th32ProcessID, pe32.szExeFile);} while (Process32Next(hProcessSnap, &pe32));// 关闭进程快照句柄CloseHandle(hProcessSnap);return 0;
}

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...