Windows编程初探(四)|进程

进程操作

常用函数

参考:https://www.bilibili.com/video/BV1By4y1r7Cq

函数名称核心功能关键参数 / 说明典型使用场景示例(简化逻辑)
CreateProcess精细创建新进程(含主线程),支持深度控制(环境、权限、窗口状态等)lpApplicationName(程序路径)、lpCommandLine(命令行参数)、lpStartupInfo(窗口 / 启动配置)需深度控制进程启动(如自动化测试、自定义环境变量、重定向输入输出)CreateProcess(L"notepad.exe", L"test.txt", ...) 启动记事本并打开文件
OpenProcess打开已有进程,获取进程句柄,为后续操作(终止、读内存等)提供权限支持dwDesiredAccess(请求权限,如 PROCESS_TERMINATE)、dwProcessId(目标 PID)操作其他进程(调试、强制终止、内存读写)OpenProcess(PROCESS_TERMINATE, ..., 1234) 获取 PID 为 1234 的进程句柄
ExitProcess主动退出当前进程,强制终止所有线程,清理进程资源uExitCode(进程退出码,供父进程识别状态)进程内部主动退出(如控制台程序收尾、GUI 程序响应退出信号)ExitProcess(0) 正常退出当前进程
TerminateProcess强制终止指定进程(需先 OpenProcess 拿句柄 ),暴力终止不清理资源hProcess(进程句柄,需 PROCESS_TERMINATE 权限)、uExitCode(退出码)紧急终止无响应进程、自动化脚本强制关闭程序(需谨慎,可能丢数据)TerminateProcess(hProc, 0) 终止通过 OpenProcess 获取的进程
WinExec简易启动程序,功能单一(兼容旧系统遗留 ),可控性弱lpCmdLine(命令行,如 "notepad.exe test.txt")、uCmdShow(窗口显示方式,如 SW_NORMAL简单启动程序,无需复杂控制(微软推荐优先用 CreateProcess )WinExec("notepad.exe test.txt", SW_NORMAL) 启动记事本打开文件
ShellExecute调用系统 Shell 执行操作,支持 “打开 / 编辑 / 打印” 文件、访问网址等系统关联行为hwnd(父窗口句柄,可为 NULL)、lpOperation(操作类型,如 "open")、lpFile(目标路径)需调用系统默认行为(如双击文件、打开网页),如软件内 “打开日志”“访问官网”ShellExecute(NULL, L"open", L"https://baidu.com", ...) 浏览器打开百度
system调用控制台命令(依赖系统 cmd.exe ),跨平台性好(C 标准库函数 )command(命令字符串,如 "notepad.exe""dir"简单执行控制台命令,或跨平台代码中启动程序 / 执行命令system("notepad.exe") 启动记事本(Windows 下);system("ls") 查看目录(Linux)
CreateToolhelp32Snapshot创建系统 “快照”,用于枚举进程、线程、模块、堆等信息dwFlags(快照类型,如 TH32CS_SNAPPROCESS 枚举进程)、th32ProcessID(目标 PID,0 枚举全部)开发 “进程管理器” 类工具,遍历系统进程、查模块、分析线程等CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 枚举所有进程

进程操作实例——遍历进程

CreateToolhelp32Snapshot函数用于创建系统快照,为什么要创建快照?因为进程随时可能发生变化,需要先创建快照固定一下。

Process32FirstW 是 Windows API 中的一个函数,用于从进程快照中获取第一个进程的信息。它是 Tool Help Library 的一部分,与 CreateToolhelp32Snapshot、Process32NextW 等函数配合使用,实现系统进程的枚举:


BOOL Process32FirstW(
  HANDLE             hSnapshot,     // 进程快照句柄(由 CreateToolhelp32Snapshot 返回)
  LPPROCESSENTRY32W  lppe           // 指向 PROCESSENTRY32W 结构体的指针,用于存储进程信息
);

PROCESSENTRY32W结构体:

typedef struct tagPROCESSENTRY32W {
  DWORD     dwSize;             // 结构体大小(必须先赋值!)
  DWORD     cntUsage;           // 进程引用计数
  DWORD     th32ProcessID;      // 进程 ID(PID)
  ULONG_PTR th32DefaultHeapID;  // 进程默认堆的 ID
  DWORD     th32ModuleID;       // 进程模块 ID
  DWORD     cntThreads;         // 进程创建的线程数
  DWORD     th32ParentProcessID;// 父进程 ID
  LONG      pcPriClassBase;     // 线程优先级
  DWORD     dwFlags;            // 保留字段
  WCHAR     szExeFile[MAX_PATH];// 进程可执行文件路径(宽字符)
} PROCESSENTRY32W;

以下代码可以实现遍历进程:

#include<Windows.h>      // Windows API 核心头文件,提供进程、线程、内存等操作接口
#include<TlHelp32.h>     // 进程/线程/模块快照工具,用于枚举系统进程

int main()
{
	// 设置控制台区域语言为中文(注意:需配合控制台编码设置才能正常显示中文)
	setlocale(LC_ALL, "chs");

	// ============== 进程枚举功能 ==============
	// 创建系统进程快照(TH32CS_SNAPPROCESS 表示获取所有进程的快照)
	// 第二个参数 0 表示针对所有进程(若填 PID 则针对单个进程)
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

	// 初始化进程信息结构体(必须设置 dwSize 字段,否则 Process32FirstW 会失败)
	PROCESSENTRY32W processEntry = { sizeof(PROCESSENTRY32W) };

	// 获取第一个进程信息(返回值为 BOOL 类型,指示是否成功),
	BOOL bSuccess = Process32FirstW(hSnapshot, &processEntry);

	// 循环遍历所有进程
	if (bSuccess) {
		do {
			// 输出进程信息:
			// - processEntry.th32ProcessID:进程 ID
			// - processEntry.szExeFile:进程可执行文件名(宽字符字符串)
			// 注意:%ls 用于格式化宽字符字符串,但需确保控制台支持 Unicode 输出
			printf("\n进程id:%d,进程名称:%ls",
				processEntry.th32ProcessID,
				processEntry.szExeFile);

		} while (Process32NextW(hSnapshot, &processEntry)); // 获取下一个进程
	}

	CloseHandle(hSnapshot);

	return 0;
}

效果:

同样的,通过下述代码可以遍历某进程的模块:

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>

int main()
{
	// 设置本地语言环境为中文(确保控制台能正确显示中文字符)
	setlocale(LC_ALL, "chs");


	// 创建 PID 为 5688 的进程的模块快照(用于枚举该进程加载的 DLL)
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 5688);
	// 初始化模块信息结构体(必须设置 dwSize 字段)
	MODULEENTRY32W moduleEntry = { sizeof(MODULEENTRY32W) };
	// 获取第一个模块信息
	BOOL bSuccess = Module32FirstW(hSnapshot, &moduleEntry);
	if (bSuccess) {
		do {
			// 输出模块路径(注意:使用 %ls 格式化宽字符字符串)
			printf("\n模块名称:%ls", moduleEntry.szExePath);
		} while (Module32NextW(hSnapshot, &moduleEntry));
	}
}

效果:

进程间通信

通信方式原理优点缺点适用场景
管道(Pipe)内核中开辟的共享缓冲区,分为匿名管道(父子进程)和命名管道(任意进程)实现简单,数据按先进先出顺序传输,有内置同步机制匿名管道仅支持父子进程,吞吐量较低,不支持双向同时通信简单数据传输,如命令行程序输出重定向
共享内存(Shared Memory)多个进程映射同一块物理内存,直接读写数据速度最快的 IPC 方式,适合大量数据传输,同步需写代码实现需要手动实现同步机制(如互斥量),权限配置不当易引发访问错误(如错误码 5)大数据量实时交换(如图像、视频数据)
消息队列(Message Queue)内核维护的消息链表,进程可发送 / 接收带类型的消息包支持消息类型分类,异步通信,解耦进程间依赖存在消息拷贝开销,性能低于共享内存异步通知、任务调度
信号量(Semaphore)计数器机制,控制对共享资源的访问权限轻量级同步工具,可控制多进程对资源的并发访问仅用于同步控制,不直接传输数据资源访问控制(如文件、数据库连接)
互斥量(Mutex)独占式锁,确保同一时间只有一个进程访问资源实现简单,自动处理线程所有权,避免死锁(部分实现)性能开销高于信号量,可能产生饥饿现象保护临界区,如共享变量访问
命名事件(Named Event)内核对象,通过状态(触发 / 未触发)通知进程事件发生支持跨进程同步,可设置自动 / 手动重置模式仅用于同步通知,不传输数据进程间状态同步(如任务完成通知)
套接字(Socket)网络编程接口,支持本地(IPC)和网络通信跨平台性好,支持远程进程通信,接口统一本地通信性能略低于专用 IPC 方式跨主机通信或本地复杂通信架构

共享内存

Sender端通过CreateFileMapping创建共享内存,然后用MapViewOfFile映射到当前内存空间即可使用该内存,往里写入信息。Receiver端打开这块共享内存,用MapViewOfFile映射到当前内存空间即可使用该内存,读出相关信息。

Sender:

#include <windows.h>
#include <iostream>
#include <string>

#define SHARED_MEM_NAME L"Local\\MyIPCSharedMemory"  // 使用 Local 命名空间
#define BUFFER_SIZE 1024

int main() {
	// 创建共享内存对象(使用默认安全属性,避免权限问题)
	HANDLE hMapFile = CreateFileMapping(
		INVALID_HANDLE_VALUE,    // 使用系统分页文件
		NULL,                    // 默认安全属性(关键修改:不使用 Global 前缀)
		PAGE_READWRITE,          // 读写权限
		0,                       // 高位文件大小
		BUFFER_SIZE,             // 共享内存大小
		SHARED_MEM_NAME          // 本地命名空间名称
	);

	if (hMapFile == NULL) {
		std::cerr << "创建共享内存失败,错误码: " << GetLastError() << std::endl;
		return 1;
	}

	// 将共享内存映射到当前进程的地址空间
	LPVOID pBuffer = MapViewOfFile(
		hMapFile,                // 共享内存句柄
		FILE_MAP_ALL_ACCESS,     // 读写访问
		0,                       // 偏移量(高位)
		0,                       // 偏移量(低位)
		BUFFER_SIZE              // 映射大小
	);

	if (pBuffer == NULL) {
		std::cerr << "映射共享内存失败,错误码: " << GetLastError() << std::endl;
		CloseHandle(hMapFile);
		return 1;
	}

	// 向共享内存写入数据
	std::string message = "Hello from Sender! 当前时间: " +
		std::to_string(GetTickCount());

	memcpy(pBuffer, message.c_str(), message.size() + 1);  // +1 包含字符串结束符
	std::cout << "已往共享内存中写入: " << message << std::endl;

	// 等待用户确认后清理资源
	std::cout << "按Enter键退出..." << std::endl;
	std::cin.get();

	// 解除映射并关闭句柄
	UnmapViewOfFile(pBuffer);
	CloseHandle(hMapFile);

	return 0;
}

Receiver:

#include <windows.h>
#include <iostream>

#define SHARED_MEM_NAME L"Local\\MyIPCSharedMemory"  // 与发送端保持一致
#define BUFFER_SIZE 1024

int main() {
	// 打开已存在的共享内存对象
	HANDLE hMapFile = OpenFileMapping(
		FILE_MAP_READ,           // 只读访问
		FALSE,                   // 不继承句柄
		SHARED_MEM_NAME          // 共享内存名称
	);

	if (hMapFile == NULL) {
		std::cerr << "打开共享内存失败,错误码: " << GetLastError() << std::endl;
		std::cerr << "请先运行发送端程序!" << std::endl;
		return 1;
	}

	// 将共享内存映射到当前进程的地址空间
	LPVOID pBuffer = MapViewOfFile(
		hMapFile,                // 共享内存句柄
		FILE_MAP_READ,           // 只读访问
		0,                       // 偏移量(高位)
		0,                       // 偏移量(低位)
		BUFFER_SIZE              // 映射大小
	);

	if (pBuffer == NULL) {
		std::cerr << "映射共享内存失败,错误码: " << GetLastError() << std::endl;
		CloseHandle(hMapFile);
		return 1;
	}

	// 从共享内存读取数据并输出
	std::cout << "从共享内存读取数据: " << static_cast<const char*>(pBuffer) << std::endl;

	// 等待用户确认后清理资源
	std::cout << "按Enter键退出..." << std::endl;
	std::cin.get();

	// 解除映射并关闭句柄
	UnmapViewOfFile(pBuffer);
	CloseHandle(hMapFile);

	return 0;
}

效果:

管道

服务器端先使用CreateNamedPipe创建命名管道,然后使用ConnectNamedPipe来阻塞等待客户端连接,客户端使用CreateFile连接管道,连接成功后服务器端使用WriteFile写入数据,然后客户端使用ReadFile来读取管道信息。(其实这里设计上存在条件竞争问题)

服务器端:

#include <windows.h>   // Windows API头文件
#include <iostream>    // 标准输入输出流
#include <string>      // 字符串处理

#define PIPE_NAME L"\\\\.\\pipe\\MyNamedPipe"  // 管道名称(必须以"\\\\.\\pipe\\"开头)

int main() {
    // 创建命名管道(服务器端)
    // 参数说明:
    // - PIPE_ACCESS_OUTBOUND:管道只用于输出(发送数据)
    // - PIPE_TYPE_MESSAGE:消息类型管道(保持消息边界)
    // - PIPE_READMODE_MESSAGE:读取模式为消息
    // - PIPE_WAIT:阻塞模式(等待客户端连接)
    HANDLE hPipe = CreateNamedPipe(
        PIPE_NAME,                  // 管道名称
        PIPE_ACCESS_OUTBOUND,       // 管道访问模式(输出)
        PIPE_TYPE_MESSAGE |         // 消息类型管道
        PIPE_READMODE_MESSAGE |     // 消息读取模式
        PIPE_WAIT,                  // 阻塞模式
        1,                          // 最大实例数
        0,                          // 输出缓冲区大小
        0,                          // 输入缓冲区大小
        0,                          // 默认超时值
        NULL                        // 默认安全属性
    );

    if (hPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "创建管道失败,错误码: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "等待客户端连接..." << std::endl;

    // 等待客户端连接(阻塞调用)
    if (ConnectNamedPipe(hPipe, NULL) != FALSE) {
        // 准备要发送的消息(包含时间戳)
        std::string message = "Hello from Named Pipe! 当前时间: " +
            std::to_string(GetTickCount());

        DWORD bytesWritten;
        // 向管道写入数据
        if (WriteFile(hPipe, message.c_str(), message.size(), &bytesWritten, NULL)) {
            std::cout << "已发送: " << message << std::endl;
        }
    }

    CloseHandle(hPipe);  // 关闭管道句柄
    return 0;
}

客户端:

#include <windows.h>
#include <iostream>

#define PIPE_NAME L"\\\\.\\pipe\\MyNamedPipe"

int main() {
    // 连接到命名管道(客户端)
    // 参数说明:
    // - GENERIC_READ:以只读模式打开管道
    // - OPEN_EXISTING:打开已存在的管道
    HANDLE hPipe = CreateFile(
        PIPE_NAME,                  // 管道名称
        GENERIC_READ,               // 只读访问
        0,                          // 不共享
        NULL,                       // 默认安全属性
        OPEN_EXISTING,              // 打开已存在的管道
        0,                          // 默认属性
        NULL                        // 不使用模板文件
    );

    if (hPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "连接管道失败,错误码: " << GetLastError() << std::endl;
        return 1;
    }

    char buffer[1024];  // 读取缓冲区
    DWORD bytesRead;    // 实际读取的字节数

    // 从管道读取数据(阻塞调用)
    if (ReadFile(hPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL)) {
        buffer[bytesRead] = '\0';  // 添加字符串结束符
        std::cout << "接收到: " << buffer << std::endl;
    }

    CloseHandle(hPipe);  // 关闭管道句柄
    return 0;
}

效果:

消息队列

接收端CreateMailslot创建消息队列,使用ReadFile的阻塞模式来等待发送端连接,发送端使用CreateFile来连接消息队列,然后使用WriteFile来往消息队列里面写入信息,接收端的ReadFile成功接收信息。

发送端:

#include <windows.h>
#include <iostream>
#include <string>

#define MAILSLOT_NAME L"\\\\.\\mailslot\\ClientQueue"  // 必须与客户端一致

int main() {
	std::cout << "服务器启动,连接客户端队列..." << std::endl;

	// 服务器端连接客户端创建的队列(发送端)
	HANDLE hMailslot = CreateFile(
		MAILSLOT_NAME,          // 客户端创建的队列名称
		GENERIC_WRITE,          // 只写权限
		FILE_SHARE_READ,        // 共享读取
		NULL,
		OPEN_EXISTING,          // 打开已存在的队列
		0,
		NULL
	);

	if (hMailslot == INVALID_HANDLE_VALUE) {
		std::cerr << "连接队列失败,错误码: " << GetLastError() << std::endl;
		return 1;
	}

	std::cout << "已连接到客户端队列,准备发送消息..." << std::endl;

	std::string message = "来自服务器的消息:这是客户端接收、服务器发送的测试";
	DWORD bytesWritten = 0;

	// 发送消息
	if (WriteFile(hMailslot, message.c_str(), message.size(), &bytesWritten, NULL)) {
		std::cout << "服务器已发送消息: " << message << std::endl;
	}
	else {
		std::cerr << "发送消息失败,错误码: " << GetLastError() << std::endl;
	}

	CloseHandle(hMailslot);
	return 0;
}

接收端:

#include <windows.h>
#include <iostream>
#include <string>

#define MAILSLOT_NAME L"\\\\.\\mailslot\\ClientQueue"  // 客户端创建的队列名称
#define BUFFER_SIZE 1024

int main() {
	std::cout << "客户端启动,创建消息队列等待服务器消息..." << std::endl;

	// 客户端创建消息队列(接收端)
	HANDLE hMailslot = CreateMailslot(
		MAILSLOT_NAME,              // 队列名称
		0,                          // 消息最大长度
		MAILSLOT_WAIT_FOREVER,      // 无限等待
		NULL
	);

	if (hMailslot == INVALID_HANDLE_VALUE) {
		std::cerr << "创建队列失败,错误码: " << GetLastError() << std::endl;
		return 1;
	}

	std::cout << "队列创建成功,等待服务器发送消息..." << std::endl;

	char buffer[BUFFER_SIZE] = { 0 };
	DWORD bytesRead = 0;

	// 读取消息(阻塞模式)
	if (ReadFile(hMailslot, buffer, BUFFER_SIZE - 1, &bytesRead, NULL)) {
		buffer[bytesRead] = '\0';
		std::cout << "客户端接收到消息: " << buffer << std::endl;
	}
	else {
		std::cerr << "读取消息失败,错误码: " << GetLastError() << std::endl;
	}

	CloseHandle(hMailslot);
	return 0;
}

效果:

从上述几个示例可以看出,这几个通信方式其实逻辑上大同小异,只不过使用的函数和底层的实现原理有区别。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇