获取进程名称:Windows句柄操作详解
GetInfoTable函数在Windows系统编程中扮演着重要的角色,尤其是在进程管理方面。其核心功能是从系统中检索关于特定进程的详细信息表。这个函数通常被应用于系统监控、性能分析、安全审计等多种场景,开发者通过调用GetInfoTable可以获取进程的ID、句柄、优先级、内存使用情况等信息。例如,系统管理员可能需要定期检查系统的进程活动,以监控恶意软件或服务的异常行为;而应用开发者则可能需要获
简介:Windows操作系统中的句柄是用于管理和操作系统资源的唯一标识符。本文将介绍如何使用非标准函数 GetInfoTable ,以及标准API函数 NtQueryInformationProcess 和 QueryFullProcessImageName ,通过句柄来获取进程名称。这些方法依赖于 OpenProcess 函数来获取访问权限,以及可能的其他技术。同时提供了示例代码片段,展示了实现过程。此外,还讨论了涉及的系统编程知识,包括进程管理、句柄操作和低级API的使用。
1. 句柄和进程管理基础
在操作系统中,进程管理是核心功能之一,它负责控制程序的执行流程以及资源分配。句柄(Handle)在Windows系统编程中是一种广泛使用的资源标识符,它是操作系统为进程或线程提供的一个引用,用于访问各种系统资源,如文件、窗口、进程等。理解句柄的概念和如何管理进程是开发高效、稳定应用程序的基础。
本章节将介绍进程管理的核心概念,包括进程的定义、进程的生命周期以及句柄在进程管理中的作用。我们还将探索操作系统的内部机制,了解进程管理如何影响系统性能和资源的高效利用。
进程作为操作系统中的独立运行实例,其状态可以经历创建、运行、挂起、终止等生命周期阶段。掌握这些概念将帮助开发者更好地管理应用程序的运行环境,确保系统的稳定性和应用的响应性。
句柄和进程管理基础的学习路径,将为读者深入探讨后续章节中的高级函数调用和进程信息查询打下坚实的基础。
2. GetInfoTable函数简介
2.1 GetInfoTable函数概述
2.1.1 函数的作用与应用场景
GetInfoTable函数在Windows系统编程中扮演着重要的角色,尤其是在进程管理方面。其核心功能是从系统中检索关于特定进程的详细信息表。这个函数通常被应用于系统监控、性能分析、安全审计等多种场景,开发者通过调用GetInfoTable可以获取进程的ID、句柄、优先级、内存使用情况等信息。
例如,系统管理员可能需要定期检查系统的进程活动,以监控恶意软件或服务的异常行为;而应用开发者则可能需要获取进程信息来优化应用程序的性能。在这些情况下,GetInfoTable函数都能够提供关键的数据支持。
2.1.2 函数的输入输出参数解析
GetInfoTable函数的标准原型如下:
BOOL GetInfoTable(DWORD dwProcessId, PROCESS_INFORMATION *pProcessInfo);
这里, dwProcessId 参数代表了目标进程的标识符,用于指定要检索信息的进程。而 pProcessInfo 是一个指向 PROCESS_INFORMATION 结构体的指针,该结构体用来存储从系统中检索到的进程信息。
输出参数 pProcessInfo 返回一个结构体,包含以下字段:
dwProcessId:进程ID。dwThreadId:线程ID。hProcess:进程的句柄。hThread:线程的句柄。
如果函数执行成功,返回值为非零值;如果失败,则返回零。
2.2 GetInfoTable函数的内部机制
2.2.1 函数如何获取进程句柄
GetInfoTable函数在内部通过调用Windows API来获取目标进程的句柄。具体来说,它使用了 OpenProcess 函数,这个函数能够打开一个现有的本地进程对象,并返回一个该进程的句柄。通过该句柄,就可以访问进程对象中的信息。
HANDLE OpenProcess(PROCESS_ACCESS_RIGHT dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
dwDesiredAccess:指定了希望获得的对进程对象的访问类型。bInheritHandle:指定了句柄是否可被继承。dwProcessId:需要打开的进程的标识符。
一旦获取了句柄,GetInfoTable函数就继续使用它来读取进程的相关信息,并将其填充到提供的 PROCESS_INFORMATION 结构体中。
2.2.2 函数如何解析进程信息
在成功获取进程句柄之后,GetInfoTable函数将调用一系列的API函数来解析进程信息。这些信息可能包括但不限于进程的运行状态、内存使用情况、句柄数量、线程数量等。
一个重要的步骤是使用 NtQueryInformationProcess API来获取进程的详细信息。这个函数有许多参数,可以指定想要检索的信息类别。例如,通过传递 ProcessBasicInformation 类别,可以获取进程的基本信息。
这里有一个示例代码段,展示如何使用 NtQueryInformationProcess 来获取进程的基本信息:
NTSTATUS NtQueryInformationProcess(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
ProcessHandle:目标进程的句柄。ProcessInformationClass:标识要检索的信息类别。ProcessInformation:指向缓冲区的指针,用于接收进程信息。ProcessInformationLength:指定缓冲区的大小。ReturnLength:返回实际写入的字节数。
在这个过程中,GetInfoTable函数确保所有需要的信息都被按照预期的方式正确解析,并存储到相应的数据结构中。
示例代码和分析
下面给出一个简单的示例,展示如何调用GetInfoTable函数,并展示其基本用法:
#include <windows.h>
#include <stdio.h>
// 定义 PROCESS_INFORMATION 结构体
typedef struct _PROCESS_INFORMATION {
DWORD dwProcessId;
DWORD dwThreadId;
HANDLE hProcess;
HANDLE hThread;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION;
// 声明 GetInfoTable 函数
BOOL GetInfoTable(DWORD dwProcessId, PPROCESS_INFORMATION pProcessInfo);
int main() {
PROCESS_INFORMATION pi = {0};
DWORD pid = GetCurrentProcessId(); // 获取当前进程ID
if (GetInfoTable(pid, &pi)) {
printf("Process ID: %u\n", pi.dwProcessId);
printf("Thread ID: %u\n", pi.dwThreadId);
// 其他信息可以按照结构体定义进行访问和打印
// ...
} else {
fprintf(stderr, "Failed to get process info.\n");
}
// 清理资源
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
在这个代码段中,我们首先包含了必要的头文件,并定义了一个 PROCESS_INFORMATION 结构体的别名。然后声明了 GetInfoTable 函数,它是我们接下来要实现或使用的函数。在 main 函数中,我们首先获取了当前进程的ID,然后创建了 PROCESS_INFORMATION 结构体实例 pi 。通过调用 GetInfoTable ,我们可以获取到当前进程的相关信息,并打印出进程ID和线程ID。最后,程序在结束前关闭了进程和线程句柄以释放资源。
函数逻辑分析
从上述代码可以看出, GetInfoTable 函数的实现涉及到以下核心逻辑:
- 调用
OpenProcess获取目标进程的句柄。 - 根据用户请求的进程信息类别,使用
NtQueryInformationProcess获取详细信息。 - 将获取到的信息填充到提供的
PROCESS_INFORMATION结构体中。 - 返回成功或失败的状态。
这里需要注意的是,错误处理和异常情况的捕获对于确保程序的稳定性和健壮性至关重要。例如,在调用 OpenProcess 和 NtQueryInformationProcess 时,应检查返回值是否表示操作成功,如果不成功,应立即进行错误处理和资源释放。
注意事项
在使用 GetInfoTable 函数时,应注意以下几点:
- 必须确保传入的
dwProcessId是有效的,并且当前进程有足够的权限来访问目标进程信息。 - 使用完进程句柄后,需要调用
CloseHandle来关闭它,避免资源泄露。 - 函数的实现需要遵循操作系统的安全和权限模型,确保只有在授权的情况下才能获取进程信息。
- 由于涉及到系统级操作,建议在开发和测试阶段充分使用调试和日志记录来确保程序行为的可预测性和安全性。
3. NtQueryInformationProcess API详解
3.1 NtQueryInformationProcess API概述
3.1.1 API的基本功能和特点
NtQueryInformationProcess是一个Windows操作系统提供的内部函数,用于查询当前进程或目标进程的详细信息。它属于Native API的一部分,因此并不在微软官方文档中进行公开说明。该API能够提供进程的多个方面的信息,例如进程权限、进程ID等。它通常用于系统级别的工具开发,如任务管理器、防病毒软件等。
3.1.2 API的调用方式和限制
NtQueryInformationProcess函数的调用方式比较特殊,它不同于标准的Win32 API调用,需要使用更底层的调用约定,即x86平台上的 __stdcall 和x64平台上的 __swiftcall 。开发者通常需要通过内联汇编、动态加载等方式来调用此API。此外,调用此API时,需要有足够的权限,否则可能会遇到权限拒绝的问题。
3.2 NtQueryInformationProcess API的参数分析
3.2.1 参数的定义和类型
函数原型如下:
NTSTATUS NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_opt_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
其中, ProcessHandle 为需要查询的进程句柄; ProcessInformationClass 表示查询信息的类别,如进程ID、进程权限等; ProcessInformation 是输出参数,根据 ProcessInformationClass 的不同,指向不同类型的结构体; ProcessInformationLength 表示 ProcessInformation 结构体的大小; ReturnLength 则是返回实际写入的数据长度。
3.2.2 参数在进程信息获取中的作用
ProcessHandle作为输入参数,确保了只有具备相应权限的进程才能查询目标进程信息。ProcessInformationClass指定了查询的类别,是API功能多样性的关键。ProcessInformation是API输出结果的存储位置,其类型由ProcessInformationClass决定。ProcessInformationLength控制了输出结果的长度,以防止缓冲区溢出。ReturnLength用于获取实际写入的数据量,有助于处理部分查询结果。
下面是一个使用NtQueryInformationProcess查询进程ID的示例代码:
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
if (hProcess == NULL) {
printf("OpenProcess failed.\n");
return 1;
}
PROCESS_BASIC_INFORMATION pbi;
NTSTATUS status = NtQueryInformationProcess(
hProcess,
ProcessBasicInformation,
&pbi,
sizeof(pbi),
NULL
);
if (status == 0) {
printf("Process ID: %d\n", pbi.UniqueProcessId);
} else {
printf("NtQueryInformationProcess failed with status %x\n", status);
}
CloseHandle(hProcess);
return 0;
}
在这段代码中,我们首先通过 OpenProcess 获取当前进程的句柄,然后调用 NtQueryInformationProcess 查询进程基本信息,其中 ProcessBasicInformation 指定了我们想要获取的进程信息类别。执行后,我们可以获得当前进程的ID。
代码分析 : - 第9行和第10行代码通过 OpenProcess 获取了当前进程的句柄,并指定了进程访问权限。 - 第16行代码定义了一个 PROCESS_BASIC_INFORMATION 结构体的变量 pbi ,这个结构体用于接收查询到的进程信息。 - 第17行到第23行代码调用 NtQueryInformationProcess ,其中第四个参数是输出信息结构体 pbi 的大小。 - 如果查询成功,那么第五行将打印出进程的ID。
在实际使用中,需要对返回的 status 值进行检查,以确保操作成功,并且需要在程序结束前关闭进程句柄,避免资源泄露。
此外,由于 NtQueryInformationProcess 属于系统底层调用,通常需要在系统调试器或安全软件中使用。开发者需要具有一定的系统编程基础和对Windows内部机制的理解。
4. OpenProcess与QueryFullProcessImageName的使用方法
4.1 OpenProcess函数的使用
4.1.1 函数的功能和必要性
在Windows系统编程中,管理进程涉及多种API函数。 OpenProcess 是这些API函数中的一个关键,它允许程序获取指定进程的句柄。通过该句柄,程序可以访问进程的地址空间,控制进程的执行等。 OpenProcess 函数的必要性在于它为开发者提供了一种权限访问和操作系统进程的机制。
4.1.2 函数的使用步骤和示例
使用 OpenProcess 时,通常遵循以下步骤:
- 确定需要操作的进程ID(PID)。
- 调用
OpenProcess函数,根据指定的权限来获取进程句柄。 - 使用该句柄执行进一步的操作,如读取内存、暂停或结束进程。
- 完成操作后,关闭进程句柄以释放系统资源。
下面是一个使用 OpenProcess 的示例代码:
#include <windows.h>
#include <stdio.h>
int main() {
DWORD pid = 1234; // 假设我们需要操作的进程ID是1234
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess != NULL) {
// 我们可以在这里进行进一步操作,比如查询进程信息或结束进程
// 示例:结束进程
TerminateProcess(hProcess, 0);
// 关闭句柄
CloseHandle(hProcess);
} else {
// 打印错误信息
printf("OpenProcess failed (%d)\n", GetLastError());
}
return 0;
}
在这段代码中,首先通过进程ID得到一个进程句柄,然后使用 TerminateProcess 函数结束进程。最后,调用 CloseHandle 函数关闭进程句柄,这是防止内存泄漏和资源占用的重要一步。
4.2 QueryFullProcessImageName函数的使用
4.2.1 函数的功能和应用场景
QueryFullProcessImageName 函数用于获取指定进程的完整映像路径。这对于安全检查、日志记录或监控工具来说是一个有用的功能,可以准确识别正在运行的进程。
4.2.2 函数的使用步骤和注意事项
要使用 QueryFullProcessImageName ,你需要遵循以下步骤:
- 获取要查询的进程句柄。
- 调用
QueryFullProcessImageName函数,传入相应的参数。 - 根据返回值验证操作是否成功,并处理获取到的路径信息。
代码示例如下:
#include <windows.h>
#include <stdio.h>
int main() {
DWORD pid = 1234; // 目标进程的PID
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess != NULL) {
TCHAR szProcessName[MAX_PATH];
if (QueryFullProcessImageName(hProcess, 0, szProcessName, NULL)) {
// szProcessName 现在包含完整路径
wprintf(L"Process full image name: %s\n", szProcessName);
} else {
// 打印错误信息
printf("QueryFullProcessImageName failed (%d)\n", GetLastError());
}
CloseHandle(hProcess); // 关闭句柄
} else {
printf("OpenProcess failed (%d)\n", GetLastError());
}
return 0;
}
这段示例代码首先尝试打开一个进程句柄,然后查询并打印出该进程的完整映像路径。如果打开句柄或查询路径失败,它将打印出错误信息。
注意事项包括:
- 确保有足够的权限来打开进程句柄和查询进程信息。
- 在查询完进程信息后,必须关闭进程句柄。
- 错误处理对于调试和保证程序的稳定性至关重要。
使用 OpenProcess 和 QueryFullProcessImageName 函数时,正确的权限管理和错误处理机制对于确保程序的正常执行非常重要。通过上述示例,可以清晰地看到每个步骤的实现和逻辑,从而在实际开发中灵活应用。
5. 编程实现示例与错误处理
在深入探讨了句柄和进程管理的基础知识,以及专门的API和函数后,本章节将通过编程实现示例,演示如何在实际编程中应用这些知识。同时,鉴于在编程中不可避免地会遇到错误,本章节还将对错误处理机制进行详细的分析。
5.1 编程实现示例
5.1.1 示例的构建和分析
为了演示如何使用 OpenProcess 和 QueryFullProcessImageName 函数,我们将构建一个简单的示例程序,该程序能够枚举系统中的进程,并显示它们的完整路径。
首先,需要声明所需的函数原型和相关的数据结构:
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
DWORD GetProcessIdByName(const char* processName) {
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
return 0;
}
Process32First(hProcessSnap, &pe32);
if (!strcmp(pe32.szExeFile, processName)) {
CloseHandle(hProcessSnap);
return pe32.th32ProcessID;
}
while (Process32Next(hProcessSnap, &pe32)) {
if (!strcmp(pe32.szExeFile, processName)) {
CloseHandle(hProcessSnap);
return pe32.th32ProcessID;
}
}
CloseHandle(hProcessSnap);
return 0;
}
int main() {
DWORD processId = GetProcessIdByName("notepad.exe");
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (hProcess == NULL) {
printf("OpenProcess failed with error: %d\n", GetLastError());
return 1;
}
TCHAR szProcessName[MAX_PATH];
szProcessName[0] = '\0';
if (QueryFullProcessImageName(hProcess, 0, szProcessName, NULL)) {
printf("Process Name: %s\n", szProcessName);
} else {
printf("QueryFullProcessImageName failed with error: %d\n", GetLastError());
}
CloseHandle(hProcess);
return 0;
}
在这段代码中, GetProcessIdByName 函数利用 CreateToolhelp32Snapshot 和 Process32First 、 Process32Next 来遍历系统中的进程,并找到指定名称的进程ID。 main 函数中,我们使用了 GetProcessIdByName 函数来获取记事本进程的ID,然后通过 OpenProcess 获取该进程的句柄。随后,我们调用 QueryFullProcessImageName 来获取该进程的完整路径,并打印出来。
5.1.2 示例的执行和结果展示
为了执行上述程序,你需要将代码保存为 .c 文件,并使用支持Windows API的C编译器,比如Microsoft Visual C++。编译后运行程序,预期的输出会是记事本程序的完整路径。如果程序没有找到记事本进程或存在其他错误,它会打印出相应的错误信息。
5.2 错误处理机制
5.2.1 常见错误类型和原因
在操作系统的进程中进行查询和控制时,可能会遇到多种错误。这些错误可能由以下原因引起:
- 权限不足:尝试访问或操作一个没有足够权限的进程。
- 进程不存在:尝试查询的进程名称不存在或进程ID不正确。
- 输入参数错误:提供的参数不符合API函数的要求,例如句柄值为
NULL。 - 系统资源不足:系统资源不足以完成请求的操作。
5.2.2 错误处理的策略和方法
为确保代码的健壮性,开发者应采取以下策略来处理错误:
- 首先,检查所有API调用的返回值。大多数Windows API在失败时会返回
NULL或特定的错误码,如GetLastError可用来获取最后一个Windows错误代码。 - 其次,实现异常处理机制。使用如
try...except块来捕获可能发生的异常情况。 - 第三,采用日志记录错误信息。记录详细的错误信息有助于事后分析问题原因。
- 最后,确保程序的鲁棒性。例如,当操作失败时,应确保释放所有已经分配的资源。
例如,在我们的示例程序中,我们已经在 OpenProcess 和 QueryFullProcessImageName 调用失败时输出了错误信息。这是一种基本的错误处理策略,可以提醒开发者和用户程序运行中出现的问题。
在此基础上,可以进一步扩展错误处理,例如,为用户提供重试或退出程序的选项,或者在后台记录错误日志以供后续分析。
本章节通过一个具体的示例展示了如何在Windows编程中处理进程信息,同时也讲解了错误处理的策略和方法,旨在帮助开发者更好地理解如何在实际应用中运用相关知识,并通过有效的错误处理机制来提高程序的稳定性和可靠性。
6. Windows系统编程在进程管理中的应用
随着IT技术的发展,系统编程在进程管理方面的应用变得越来越重要。Windows作为一个成熟的操作系统,提供了丰富的API接口,以供开发者进行深入的系统级别操作。本章将详细介绍Windows系统编程在进程管理中的高级应用和实践意义。
6.1 进程管理的高级应用
6.1.1 进程的创建和销毁
在Windows系统编程中,进程的创建和销毁是常见的操作。我们可以利用 CreateProcess 函数创建新的进程,通过 TerminateProcess 函数来销毁一个进程。
以下是一个简单的示例,展示了如何在C++中使用这些函数:
#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
int main() {
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// 创建进程
if (!CreateProcess(NULL, // 模块名称
"notepad.exe", // 命令行
NULL, // 进程句柄不可继承
NULL, // 线程句柄不可继承
FALSE, // 设置句柄继承选项
0, // 没有创建标志
NULL, // 使用父进程的环境块
NULL, // 使用父进程的起始目录
&si, // 指向STARTUPINFO结构
&pi) // 指向PROCESS_INFORMATION结构
) {
std::cerr << "CreateProcess failed (" << GetLastError() << ").\n";
return -1;
}
// 等待进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
// 销毁进程
if (!TerminateProcess(pi.hProcess, 0)) {
std::cerr << "TerminateProcess failed (" << GetLastError() << ").\n";
}
// 关闭进程和线程句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
在这个示例中,我们启动了记事本程序,并在程序运行后立即终止它。需要注意的是, TerminateProcess 会强制结束进程,可能会导致资源未正确释放和数据丢失。
6.1.2 进程的权限控制和安全
进程权限控制是安全领域的重要组成部分。Windows通过访问控制列表(ACLs)和安全描述符来控制进程的访问权限。我们可以通过修改这些安全属性来实现权限控制。
权限控制通常涉及到修改对象的访问令牌,这可以通过 AdjustTokenPrivileges 函数实现:
BOOL EnablePrivilege(HANDLE hToken, LPCTSTR PrivilegeName, BOOL bEnablePrivilege) {
TOKEN_PRIVILEGES tp;
LUID luid;
TOKEN_PRIVILEGES tpPrevious;
// 获取特定权限的LUID
if (!LookupPrivilegeValue(NULL, PrivilegeName, &luid))
return FALSE;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0;
// 将权限调整到进程
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &tpPrevious, NULL))
return FALSE;
return TRUE;
}
通过调整权限,我们可以控制对系统资源的访问,增强系统的安全性。
6.2 Windows系统编程的实践意义
6.2.1 在系统维护中的应用
在系统维护方面,系统编程可以帮助我们进行进程监控、资源管理和错误诊断。例如,通过 CreateToolhelp32Snapshot 、 Process32First 和 Process32Next 函数,我们可以枚举系统中的所有进程,并对每个进程进行检查。
6.2.2 在安全领域的应用
在安全领域,系统编程能力可以用于实现安全策略,例如实现用户权限验证、审计日志记录和病毒防护。这些功能通常是通过修改系统内核或实现低级API调用来完成的。
例如,通过编程实现的自定义杀毒软件可能会使用 ReadProcessMemory 和 WriteProcessMemory 函数来分析和清理被感染的进程。
小结
本章深入探讨了Windows系统编程在进程管理中的高级应用,包括进程的创建和销毁、权限控制以及在系统维护和安全领域的实践应用。理解这些概念和方法对于IT专业人员来说是极其重要的,不仅能够提升工作效率,还能为系统管理和安全防护提供强大的技术支持。
简介:Windows操作系统中的句柄是用于管理和操作系统资源的唯一标识符。本文将介绍如何使用非标准函数 GetInfoTable ,以及标准API函数 NtQueryInformationProcess 和 QueryFullProcessImageName ,通过句柄来获取进程名称。这些方法依赖于 OpenProcess 函数来获取访问权限,以及可能的其他技术。同时提供了示例代码片段,展示了实现过程。此外,还讨论了涉及的系统编程知识,包括进程管理、句柄操作和低级API的使用。
更多推荐

所有评论(0)