实验要求

使用Windows核心API实现以下程序

  1. 程序1:该程序能够创建一个本机的notepad进程(使用CreateProcess,显式指定notepad可执行文件的路径)

  2. 程序2:

    a. 该程序能够创建一个线程,显示MessageBox

    b. 在以上子线程中,编程获得kernel32.dll在当前系统中的路径信息,作为内容显示在以上的MessageBox中

    c. 在以上子线程中,编程获得子线程所加载kernel32.dll中的GetCurrentThreadId(. 函数的地址,调用该函数,获得子线程的线程编号,将线程编号连接到上一问的kernel32.dll路径后面,再将连接结果字符串显示在MessageBox中

    d. 最终显示结果如下图所示:

    ![](https://pic1.imgdb.cn/item/68f9e3273203f7be0091ccc9.png)

程序1设计与实现:创建Notepad进程

设计思路

通过Windows API CreateProcess创建记事本进程,需显式指定notepad.exe的完整路径(C:\\Windows\\notepad.exe),并配置进程启动参数(窗口显示)。

核心代码(Program1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <windows.h>
#include <iostream>

int main() {
// 显式指定notepad.exe路径
LPCWSTR appPath = L"C:\\Windows\\notepad.exe";
// 进程与线程安全属性(默认)
LPSECURITY_ATTRIBUTES procAttr = NULL;
LPSECURITY_ATTRIBUTES threadAttr = NULL;
BOOL inheritHandles = FALSE;
DWORD creationFlags = 0; // 无特殊标志
LPVOID environment = NULL; // 继承父进程环境
LPCWSTR currentDir = NULL; // 继承父进程工作目录

// 启动信息结构体(配置窗口显示)
STARTUPINFOW si = {0}; // 使用 STARTUPINFOW
si.cb = sizeof(STARTUPINFOW); // 必须初始化大小
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW; // 显示窗口

// 接收进程信息的结构体
PROCESS_INFORMATION pi = {0};

// 调用CreateProcessW创建进程(注意函数名称末尾的"W")
BOOL success = CreateProcessW(
appPath, // 显式指定可执行文件路径
NULL, // 命令行参数(此处无需)
procAttr,
threadAttr,
inheritHandles,
creationFlags,
environment,
currentDir,
&si, // 对应的结构体也应该是宽字符版本
&pi
);

if (!success) {
std::cerr << "创建进程失败,错误码:" << GetLastError() << std::endl;
return 1;
}

// 关闭不需要的句柄(避免资源泄漏)
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}

关键语句说明

  • CreateProcess:Windows创建进程的核心API。第一个参数appPath显式指定notepad.exe路径,确保系统准确定位可执行文件。
  • STARTUPINFO:用于配置进程启动时的窗口状态,wShowWindow = SW_SHOW确保记事本窗口可见,cb成员必须设置为结构体大小,否则API调用失败。
  • PROCESS_INFORMATION:接收创建的进程和主线程的句柄及ID,使用后需通过CloseHandle释放句柄,避免系统资源泄漏。

程序2设计与实现:线程创建与信息显示

设计思路

  • 子线程创建:通过CreateThread创建子线程,线程函数负责执行信息获取与显示逻辑。
  • 获取kernel32.dll路径:利用GetModuleHandle获取kernel32.dll的模块句柄(该DLL默认加载到所有进程),再通过GetModuleFileName获取其完整路径。
  • 获取线程ID:通过GetProcAddress获取kernel32.dllGetCurrentThreadId函数的地址,调用该函数获取当前线程ID,拼接路径后通过MessageBox显示。

核心代码(Program2.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
#include <string>
using namespace std;

// 线程函数:完成信息获取与显示
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
// 步骤b:获取kernel32.dll路径
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
if (hKernel32 == NULL) {
MessageBox(NULL, L"获取kernel32句柄失败", L"错误", MB_OK | MB_ICONERROR);
return 1;
}

WCHAR dllPath[MAX_PATH] = {0};
DWORD pathLen = GetModuleFileNameW(hKernel32, dllPath, MAX_PATH);
if (pathLen == 0) {
MessageBox(NULL, L"获取DLL路径失败", L"错误", MB_OK | MB_ICONERROR);
return 1;
}

// 步骤c:获取GetCurrentThreadId函数地址并调用
FARPROC pFunc = GetProcAddress(hKernel32, "GetCurrentThreadId");
if (pFunc == NULL) {
MessageBox(NULL, L"获取函数地址失败", L"错误", MB_OK | MB_ICONERROR);
return 1;
}

// 转换函数指针并调用,获取线程ID
typedef DWORD (WINAPI *GetCurrentThreadIdFunc)();
GetCurrentThreadIdFunc pGetThreadId = (GetCurrentThreadIdFunc)pFunc;
DWORD threadId = pGetThreadId();

// 拼接路径和线程ID(格式:路径;ID:xxxx)
std::wstring msg = std::wstring(dllPath) + L";ID:" + std::to_wstring(threadId);

// 步骤a:显示MessageBox
MessageBoxW(NULL, msg.c_str(), L"Win.prog", MB_OK);
return 0;
}

int main() {
// 创建子线程
HANDLE hThread = CreateThread(
NULL, // 默认安全属性
0, // 默认栈大小
ThreadFunc, // 线程函数
NULL, // 传递给线程的参数(无)
0, // 立即执行线程
NULL // 不接收线程ID
);

if (hThread == NULL) {
MessageBox(NULL, L"创建线程失败", L"错误", MB_OK | MB_ICONERROR);
return 1;
}

// 等待子线程执行完毕
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread); // 释放线程句柄
return 0;
}

关键语句说明

  • 线程创建CreateThread的第三个参数ThreadFunc为线程入口函数,主线程通过WaitForSingleObject等待子线程完成,避免主线程提前退出导致子线程被终止。
  • 获取kernel32.dll路径
    • GetModuleHandle(L"kernel32.dll"):获取kernel32.dll的模块句柄(因kernel32.dll是系统核心DLL,所有进程都会加载,故无需显式加载即可获取)。
    • GetModuleFileName(hKernel32, dllPath, MAX_PATH):通过模块句柄获取DLL在系统中的完整路径,存储于dllPath缓冲区。
  • 获取函数地址与调用
    • GetProcAddress(hKernel32, "GetCurrentThreadId"):根据函数名从kernel32.dll中获取GetCurrentThreadId的地址(该函数用于返回当前线程ID)。
    • 函数指针转换:通过typedef定义函数指针类型GetCurrentThreadIdFunc,将FARPROC类型转换为具体函数指针,确保调用时参数和返回值匹配。
  • 信息显示:拼接kernel32.dll路径和线程ID为宽字符串,通过MessageBox显示,标题为"Win.prog",按钮为“确定”,与题目要求的效果图一致。

运行结果

  1. 程序1:成功启动记事本进程,窗口正常显示。
  2. 程序2:弹出MessageBox,如图所示:

    MessageBaox

总结

  1. CreateProcess需正确配置可执行文件路径和启动信息,句柄使用后需及时释放。
  2. 线程创建需注意同步(如WaitForSingleObject),避免主线程提前退出。
  3. GetModuleHandleGetModuleFileNameGetProcAddress是操作DLL的核心API,可灵活获取模块路径和函数地址,实现动态调用。

通过本实验,深入理解了Windows进程/线程管理及DLL动态链接机制,掌握了核心API的使用方法。