windows C++ 进程遍历、线程遍历模板。后附模板代码

       

目录

一、进程遍历

1. 使用CreateToolhelp32Snapshot函数创建系统快照

2.Process32FirstW  

3. Process32NextW

4.  PROCESSENTRY32W

5. OpenProcess

hProcess

1. 查询信息

2. 修改进程状态

3. 访问进程虚拟内存

4. 处理和线程

二、线程遍历

1. 创建系统快照

2. 遍历线程

3. 关闭快照句柄

4. 线程句柄

THREADENTRY32 结构体成员

OpenThread

参数 

示例代码 

hThread

5. 获取某进程内的线程信息


        进程或线程遍历是windows软件必备需求,从安装到软件运行再到卸载我们或许都需要知道其他进程的信息。

一、进程遍历

        遍历进程主要可以用到三个小点:


1. 使用CreateToolhelp32Snapshot函数创建系统快照

        这个函数可以捕获系统当前状态的一个快照,包括所有运行中的进程、线程、模块等。为了遍历进程,需要指定TH32CS_SNAPPROCESS标志。

        他有两个参数,一个是要遍历的对象,另一个是ID信息。

HANDLE CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID
);

dwFlags (DWORD): 指定快照中包含的内容。这是一个位字段的标志,可以组合使用。常用的标志包括:

  • TH32CS_SNAPPROCESS: 包含系统中所有进程的快照。
  • TH32CS_SNAPTHREAD: 包含系统中所有线程的快照。
  • TH32CS_SNAPMODULE: 包含指定进程关联的所有模块(DLL和驱动程序)的快照。要使用此标志,th32ProcessID参数必须是有效的进程标识符。
  • TH32CS_SNAPHEAPLIST: 包含指定进程的所有堆的快照。同样,需要有效的进程标识符。
  • TH32CS_SNAPALL: 包括所有以上提到的快照。
  • TH32CS_INHERIT: 指示返回的快照句柄是可继承的。

th32ProcessID (DWORD): 指定要获取快照的进程的进程标识符(PID)。如果dwFlags参数包括TH32CS_SNAPMODULETH32CS_SNAPHEAPLIST,则此参数必须是目标进程的PID。对于其他类型的快照,通常设置为0,表示获取所有进程的信息。

以下每一项在api的调用非常关键:

使用注意事项

  • 使用完成后,应通过调用CloseHandle函数来关闭快照句柄,避免资源泄漏
  • 快照是在特定时间点捕获的系统状态的静态视图。系统状态在快照之后可能会发生变化
  • 在遍历模块或堆时,需要确保目标进程的PID有效,否则快照将不会包含这些信息。
  • 该函数可能会因权限不足而失败,特别是尝试访问系统进程或高权限进程的模块和堆信息时。

2.Process32FirstW  

  Process32FirstW 函数是 Windows API 的一部分,用于遍历系统中的进程。此函数与 CreateToolhelp32Snapshot 函数配合使用,CreateToolhelp32Snapshot 函数用于创建系统当前状态的快照,而 Process32FirstW 则用于获取快照中的第一个进程的信息。Process32FirstW 的 "W" 表示该函数用于处理宽字符(Unicode)字符串,与之对应的还有 Process32First 函数,用于处理ANSI字符串。 

3. Process32NextW

        Process32NextW 函数是 Windows API 的一部分,用于继续遍历 CreateToolhelp32Snapshot 函数创建的快照中的进程信息。在调用 Process32FirstW 获取快照中第一个进程的信息之后,Process32NextW 被用来遍历和获取快照中剩余进程的信息。同样地,Process32NextW 的 "W" 后缀表示该函数处理宽字符(Unicode)字符串。

在Windows操作系统中,使用CreateToolhelp32SnapshotProcess32FirstWProcess32NextW这一组API来遍历系统进程时,进程被遍历的顺序并没有明确的、文档化的保证。这意味着Windows并没有承诺会按照某种特定顺序(如进程ID排序、创建时间顺序等)来返回进程信息。实际上,进程的遍历顺序取决于操作系统的内部实现细节,这些细节可能会在不同版本的Windows中有所不同,甚至可能会在相同版本的Windows中因为更新或配置的改变而有所变化。

代码示例: 

#include <windows.h>
#include <tlhelp32.h>
#include <iostream>// 函数声明:遍历所有进程并打印信息
void EnumerateProcesses() {// 创建系统快照HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (hSnapshot == INVALID_HANDLE_VALUE) {std::wcerr << L"CreateToolhelp32Snapshot failed." << std::endl;return;}PROCESSENTRY32W pe32;   //pe32中有一些信息可供使用者参考,在这加个关键判断pe32.dwSize = sizeof(PROCESSENTRY32W);// 获取快照中的第一个进程if (!Process32FirstW(hSnapshot, &pe32)) {std::wcerr << L"Process32First failed." << std::endl;CloseHandle(hSnapshot);return;}// 遍历所有进程do {// 输出进程的ID和名称(使用宽字符)// 此处可以用ID打开此进程详细信息,OpenProcessstd::wcout << L"Process ID: " << pe32.th32ProcessID << L"\tProcess Name: " << pe32.szExeFile << std::endl;} while (Process32NextW(hSnapshot, &pe32));CloseHandle(hSnapshot);
}int main() {EnumerateProcesses();return 0;
}

4.  PROCESSENTRY32W

PROCESSENTRY32W pe32;  中PROCESSENTRY32W结构介绍:

typedef struct tagPROCESSENTRY32W {DWORD   dwSize;DWORD   cntUsage;DWORD   th32ProcessID;ULONG_PTR th32DefaultHeapID;DWORD   th32ModuleID;DWORD   cntThreads;DWORD   th32ParentProcessID;LONG    pcPriClassBase;DWORD   dwFlags;WCHAR   szExeFile[MAX_PATH];
} PROCESSENTRY32W;

成员介绍

  • dwSize: 结构体的大小,以字节为单位。在调用 Process32FirstWProcess32NextW 之前,必须设置为 sizeof(PROCESSENTRY32W)
  • cntUsage: 进程的引用计数(不再被广泛使用,通常为0)。
  • th32ProcessID: 进程的唯一标识符,即PID。
  • th32DefaultHeapID: 进程默认堆的标识符(在大多数现代Windows版本中不再使用,一般不必关注)。
  • th32ModuleID: 进程的模块标识符(不再被广泛使用,通常为0)。
  • cntThreads: 进程中的线程数。
  • th32ParentProcessID: 创建此进程的父进程的PID。
  • pcPriClassBase: 进程的基本优先级。
  • dwFlags: 进程的标志(目前没有广泛使用的标志,通常为0)。
  • szExeFile: 进程的可执行文件名,使用宽字符表示。MAX_PATH 通常定义为 260,这意味着路径名最多可以包含259个字符加上一个终止的空字符。

        szExeFile就是进程exe的名称,例如UYT.exe这种。

  th32ParentProcessID这个可以知道是哪个进程ID创建的该进程,当然是上个时刻的进程此刻可能已经消亡,因此需要保证父进程一直不死才有利用价值,需要斟酌。

5. OpenProcess

HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL  bInheritHandle,DWORD dwProcessId
);
  • dwDesiredAccess (DWORD): 访问权限标志,指定打开对象时所需的访问权限。这些权限控制对进程的访问类型。常见的访问权限包括:

    • PROCESS_ALL_ACCESS: 请求对进程的所有可能的访问权限。
    • PROCESS_QUERY_INFORMATION: 需要此权限来使用GetExitCodeProcessGetPriorityClass
    • PROCESS_VM_READ: 使用此权限可读取进程的虚拟内存,例如,通过ReadProcessMemory
    • PROCESS_VM_WRITEPROCESS_VM_OPERATION: 写入权限和进行内存操作所需的权限,如WriteProcessMemory
    • 更多权限选项可在Microsoft官方文档中找到。
  • bInheritHandle (BOOL): 指示返回的句柄是否可以被进程的子进程继承。如果此参数为TRUE,句柄是可继承的。

  • dwProcessId (DWORD): 要打开的进程的标识符(PID)。每个进程在系统中都有一个唯一的标识符,可以通过任务管理器、CreateToolhelp32Snapshot函数等方法获得。

  • 指定的访问权限应仅限于应用程序实际需要的,过多的权限可能会增加安全风险。
  • 即使拥有足够的权限,某些进程(特别是系统进程或拥有更高权限的进程)可能仍然无法打开。
  • 使用完毕后,应通过调用CloseHandle函数关闭句柄,以避免资源泄露。
  • PROCESS_ALL_ACCESS在不同版本的Windows中可能有不同的定义,因此在跨版本使用时应谨慎。

 HANDLE  hProcess = OpenProcess(1,2,3),拿到这个hProcess,我们可以对进程干很多事情。

hProcess

1. 查询信息
  • 进程退出代码:使用 GetExitCodeProcess 函数,你可以获取进程的退出代码,前提是进程已结束。

  • 进程优先级类GetPriorityClass 函数可以返回进程的优先级类别。

  • 进程工作集信息GetProcessWorkingSetSize 函数用于获取进程的最小和最大工作集大小。

  • 进程时间信息GetProcessTimes 函数提供了进程的创建时间、退出时间、内核模式时间和用户模式时间。

  • 进程内存使用信息GetProcessMemoryInfo 函数(需包含头文件 "psapi.h" 并链接 Psapi.lib 库)可以获取进程的内存使用情况,如工作集大小、页面错误次数等。

  • 进程令牌OpenProcessToken 函数可以打开进程的访问令牌,用于获取或修改进程的安全属性,如权限等。

  • 进程I/O统计数据GetProcessIoCounters 函数提供了进程的I/O操作统计数据,包括读写操作的次数和字节数。

  • 进程亲和力GetProcessAffinityMask 函数用于获取进程的处理器亲和力设置,即进程可以在哪些处理器上运行。

  • 查询内存统计信息QueryWorkingSetQueryWorkingSetEx 函数可以用来查询进程的工作集中页面的信息。这可以帮助开发者理解哪些页面被载入到物理内存中。

  • 进程的虚拟内存信息VirtualQueryEx 函数可以查询指定进程的虚拟地址空间的状态信息,包括区域的保护属性、物理内存占用等。

  • 获取进程环境:虽然没有直接的API来获取另一个进程的完整环境块,但可以通过读取进程内存的方式(结合 ReadProcessMemory 和进程PEB结构)来间接获得。

  • 获取进程的可执行文件路径QueryFullProcessImageName 函数可以获取进程的完整路径。这对于确定正在运行的程序非常有用。

  • 进程性能信息GetPerformanceInfo 函数可以提供系统或进程的性能信息,如缓存的统计数据、物理和虚拟内存的使用情况等。

  • 查询进程安全信息GetSecurityInfoGetKernelObjectSecurity 函数允许查询进程对象的安全描述符,包括所有者、DACL(访问控制列表)等信息。

  • 修改进程DACL:结合 SetSecurityInfoSetKernelObjectSecurity 可以修改进程的安全描述符,调整其访问控制策略。

  • 枚举进程句柄NtQuerySystemInformation(未公开文档的函数,可通过动态链接)和 NtQueryObject(同样未公开)可以用来枚举进程打开的句柄和句柄指向的对象类型,虽然这需要一定的权限和稳定性考虑。

  • 进程完整性级别:通过 GetTokenInformation 函数(和进程令牌一起使用)可以查询进程的完整性级别,这对于理解进程的安全上下文很重要。

  • 获取和设置进程的DEP(数据执行防止)设置GetProcessDEPPolicySetProcessDEPPolicy 函数用于查询和配置进程的DEP策略,增强安全性。

  • 进程句柄计数GetProcessHandleCount 函数可以获取进程当前打开的句柄数量。

        信息提供了对进程更深入的监控和管理能力。使用这些API时,需要确保你的程序拥有相应的权限来访问进程信息,尤其是对于那些可能会改变进程状态或安全设置的操作。开发时还应注意API的版本兼容性和适用性,因为某些API可能在不同版本的Windows上表现不同,或者需要特定的系统权限

2. 修改进程状态
  • 设置进程优先级SetPriorityClass 函数允许你改变进程的优先级类别。

  • 调整工作集大小SetProcessWorkingSetSize 函数可以调整进程的工作集大小,这是进程可以使用的物理内存量的最小值和最大值。

  • 修改亲和力SetProcessAffinityMask 函数用于设置进程的处理器亲和力,指定进程可以运行的CPU核心。

  • 终止进程TerminateProcess 函数可以用于强制结束进程。

  • 挂起和恢复进程:通过 NtSuspendProcessNtResumeProcess 函数(这些函数不直接在Windows API文档中公开,但通过动态调用可用)来挂起和恢复进程的执行。

  • 注入DLLCreateRemoteThread 结合 LoadLibrary 方法可以用来向目标进程注入DLL。这通常通过创建一个远程线程来执行 LoadLibrary 函数,并将DLL路径作为参数传递给目标进程的地址空间中。

  • 卸载DLL:类似地,可以通过创建一个远程线程来调用 FreeLibrary,以从目标进程中卸载之前注入的DLL。

  • 修改内存保护VirtualProtectEx 函数用于改变进程虚拟地址空间中一页或多页的内存保护属性。这在修改代码段或需要特殊内存保护的情况下非常有用。

  • 分配和释放内存VirtualAllocExVirtualFreeEx 函数可以在目标进程的地址空间内分配或释放内存区域。这对于注入代码或数据到进程中特别有用。

  • 调整权限:如果拥有对进程的访问令牌句柄,可以使用 AdjustTokenPrivileges 函数来启用或禁用指定的权限。这对于执行需要高权限的操作(比如关机)是必需的。

  • 更改进程安全性:使用 SetSecurityInfoSetKernelObjectSecurity 函数可以修改进程对象的安全描述符,包括更改其DACL来控制对进程的访问。

  • 创建进程CreateProcess 函数可以用于创建新的进程。虽然这不是直接修改一个现有进程的状态,但你可以通过创建进程时的继承选项和启动参数来间接影响进程行为。

  • 进程组和作业:通过将进程添加到作业对象(使用 AssignProcessToJobObject 函数),可以对一组进程施加限制或策略,如CPU时间限制、工作集大小限制等。这可以间接修改进程的运行状态。

  • 调试进程:使用 DebugActiveProcess 函数可以附加到一个正在运行的进程上作为其调试器。这允许监视和控制目标进程的执行,包括在发生异常时接收通知。

  • 生成异常RaiseException 函数可以在当前进程中引发异常。如果有调试器附加到进程,这可能会改变进程的执行流程。

  • 设置IO优先级:通过 NtSetInformationProcess 函数(未公开文档的函数,可通过动态链接)可以设置进程的IO优先级。这对于控制进程对IO资源的使用非常有用。

3. 访问进程虚拟内存
  • 读取内存ReadProcessMemory 函数允许从打开的进程中的指定内存地址读取数据。

  • 写入内存WriteProcessMemory 函数可以修改打开的进程中的指定内存地址处的数据。

  • 查询内存信息VirtualQueryEx 函数用于查询进程的虚拟地址空间中的信息。

  • 分配内存

    • VirtualAllocEx:在目标进程的虚拟地址空间内分配内存。这对于在进程中注入数据或代码非常有用。
  • 释放内存

    • VirtualFreeEx:用于释放或解除目标进程虚拟地址空间内的内存区域。与 VirtualAllocEx 配合使用,可以管理进程内存的生命周期。
  • 修改内存保护

    • VirtualProtectEx:改变目标进程中一页或多页内存区域的保护属性。这在修改执行代码或需要调整内存访问权限时非常重要。
  • 内存映射和模块信息

    • EnumProcessModulesEnumProcessModulesEx:这些函数用于列出进程加载的所有模块(DLL文件和可执行文件)。可以用于获取模块基址,进而读取或修改模块内的数据。
    • GetModuleInformation:获取指定模块的信息,如其大小、入口点和模块基址。
  • 详细内存状态和统计

    • GetProcessMemoryInfo:提供了进程的内存使用信息,包括页面错误次数、工作集大小等。需要包含 "psapi.h" 头文件并链接 Psapi.lib
    • GlobalMemoryStatusEx:虽然不是特定于单一进程,但此函数提供了系统级别的内存使用情况,包括总体物理和虚拟内存使用量。
  • 内存优化

    • EmptyWorkingSet:将指定进程的工作集(即常驻内存集)清空,强制页面文件出所有物理内存页。这可以在某些情况下用来减少进程的物理内存需求,但可能会增加页面错误和性能开销。
  • 内存页面优先级

    • SetProcessWorkingSetSize:除了调整工作集大小外,这个函数还可以影响进程的页面优先级,间接影响其内存页是否被交换出物理内存。
  • 查询和修改内存
  • NtQueryVirtualMemory:虽然未公开文档,但通过动态调用可以使用,用于获取关于进程虚拟内存的更详细信息,如内存区域的状态、类型和访问权限。
  • NtWriteVirtualMemoryNtReadVirtualMemory:同样是未公开的函数,通过动态调用提供更底层的内存读写能力,可能在某些特殊情况下被用于调试或逆向工程。

4. 处理和线程
  • 创建线程CreateRemoteThread 函数允许在打开的进程中创建一个新线程。

  • 调整令牌权限:使用进程令牌句柄(通过 OpenProcessToken 获取)配合 AdjustTokenPrivileges 函数可以修改进程令牌的权限,这对于执行需要提升的操作非常重要。

  • 挂起和恢复线程

    • SuspendThreadResumeThread:这两个函数可以挂起和恢复单个线程的执行。这对于调试、单步执行或在修改线程上下文之前暂停线程执行非常有用。
  • 设置线程优先级

    • SetThreadPriority: 可以调整线程的执行优先级。这对于控制应用程序中不同线程的执行相对重要性非常有用。
  • 获取和设置线程上下文

    • GetThreadContextSetThreadContext:这些函数允许获取和设置线程的寄存器等上下文信息,通常用于调试器中。
  • 结束线程

    • TerminateThread:强制结束指定线程。使用时需要谨慎,因为它不会保证线程资源的正确清理。
  • 获取安全令牌信息

    • GetTokenInformation:可以获取进程令牌的详细信息,包括但不限于权限、用户账号信息和完整性级别。
  • 创建进程与令牌

    • CreateProcessAsUser:使用指定用户令牌创建新进程。这对于实现在不同用户上下文中执行操作非常有用。
  • 令牌复制和修改

    • DuplicateTokenEx:创建现有令牌的新副本,并可以设置新令牌的访问级别。这在需要修改令牌权限以执行特定操作时非常有用。
  • 高级进程创建

    • CreateProcessWithTokenWCreateProcessWithLogonW:这些函数允许以特定用户身份创建新进程,提供更细粒度的控制,例如指定日志记录用户或使用特定的令牌。
  • 线程本地存储(TLS)

    • TlsAlloc, TlsGetValue, TlsSetValue, TlsFree:这组函数用于管理线程本地存储,允许线程存储和检索对于每个线程都是唯一的数据。
  • 进程和线程同步

    • 同步对象如互斥体(Mutex)、信号量(Semaphore)、事件(Event)和临界区(CriticalSection)等可以在进程和线程之间同步操作。
  • 进程间通信(IPC)

    • 管道(Pipes)、邮槽(Mailslots)、共享内存和远程过程调用(RPC)等机制允许在不同进程之间交换数据。

二、线程遍历

1. 创建系统快照

        首先,你需要创建一个系统快照(System Snapshot),这个快照包含了系统当前时刻所有活动线程的信息。CreateToolhelp32Snapshot函数用于创建系统快照。

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {// 错误处理
}

2. 遍历线程

        创建快照后,可以使用Thread32FirstThread32Next函数遍历快照中的线程条目。

THREADENTRY32 te;
te.dwSize = sizeof(THREADENTRY32);if (Thread32First(hSnapshot, &te)) {do {// 使用te.th32ThreadID来获取线程ID// 可以根据需要做进一步的处理,例如获取线程详细信息等} while (Thread32Next(hSnapshot, &te));
} else {// 错误处理
}

3. 关闭快照句柄

        在完成线程遍历后,不要忘记关闭快照句柄,以释放系统资源。

CloseHandle(hSnapshot);

4. 线程句柄

  • 获取线程详情:可以使用线程ID与其他API结合,获取更多线程的详细信息,如线程优先级、所属进程等。
  • 线程操作:拥有线程ID后,可以对线程执行各种操作,如挂起、恢复、终止等。

THREADENTRY32 结构体成员

  • dwSize:结构体的大小,以字节为单位。在调用Thread32First之前,必须设置此字段的值为sizeof(THREADENTRY32)。这样做可以让函数知道内存的大小,以防止缓冲区溢出。
  • cntUsage:线程的引用计数,通常不用于线程遍历。
  • th32ThreadID:线程的标识符。这是一个唯一的值,可以用来标识系统中的线程。
  • th32OwnerProcessID:拥有线程的进程标识符。这个值指示了线程所属的进程。
  • tpBasePri:线程的基本优先级。线程的实际优先级是在基本优先级的基础上,加上进程的优先级类的偏移量。
  • tpDeltaPri:线程的优先级变化值。这个值表示了线程的优先级自上次调度以来的变化量。
  • dwFlags:保留字段,目前未使用。

OpenThread

  OpenThread 是一个由Windows API提供的函数,用于打开一个现有线程的句柄。通过这个句柄,程序可以对线程进行各种操作,比如查询线程状态、暂停或恢复线程执行、设置线程的优先级等。使用OpenThread函数时,你需要指定想要执行的操作的访问权限和线程的唯一标识符(线程ID)。

HANDLE OpenThread(DWORD dwDesiredAccess,  // 需要的访问权限BOOL  bInheritHandle,   // 句柄继承选项DWORD dwThreadId        // 线程标识符
);
参数 
  • dwDesiredAccess:指定打开线程时所需的访问权限。这些权限包括但不限于THREAD_SUSPEND_RESUME(暂停或恢复线程执行)、THREAD_GET_CONTEXT(获取线程的上下文,适用于在调试器中)、THREAD_SET_CONTEXTTHREAD_QUERY_INFORMATION(查询线程信息)等。THREAD_ALL_ACCESS提供了对线程的全部访问权限。
  • bInheritHandle:如果这个值为TRUE,表示被创建的句柄可以被当前进程的子进程继承。
  • dwThreadId:要打开的线程的标识符。可以通过CreateThread返回的线程ID或THREADENTRY32结构体中的th32ThreadID成员获得。
示例代码 
#include <windows.h>
#include <iostream>int main() {DWORD threadId = // 你需要操作的线程IDHANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadId);if (hThread == NULL) {std::cerr << "Failed to open thread: " << GetLastError() << std::endl;return 1;}// 暂停线程SuspendThread(hThread);// 执行某些操作...// 恢复线程ResumeThread(hThread);// 关闭线程句柄CloseHandle(hThread);return 0;
}
hThread

        拿到hThread后:

1. 控制线程执行
暂停和恢复线程:使用SuspendThread和ResumeThread函数可以暂停和恢复线程的执行。这在需要控制线程执行流程,或在特定时刻暂停线程执行以便进行某些操作时非常有用。
2. 获取和设置线程优先级
获取线程优先级:GetThreadPriority函数可以用来获取线程的当前优先级。
设置线程优先级:SetThreadPriority函数允许你改变线程的优先级,这可以影响线程的调度。
3. 线程同步
等待线程结束:WaitForSingleObject或WaitForMultipleObjects函数可以等待线程结束。这在需要同步线程执行或确保线程完成特定任务后再继续执行其他操作时非常有用。
4. 线程上下文操作
获取和设置线程上下文:GetThreadContext和SetThreadContext函数允许你获取或设置线程的上下文信息(仅限于使用在挂起的线程)。这主要用于调试器等需要精确控制线程执行环境的高级应用。
5. 线程局部存储
线程局部存储(TLS)操作:TlsAlloc、TlsGetValue、TlsSetValue和TlsFree函数允许你为每个线程分配和管理TLS索引,用于存储和访问线程特定数据。
6. 线程安全的退出
终止线程:TerminateThread函数可用于强制结束线程的执行。但使用此函数需要非常小心,因为它不保证线程资源的正确清理和释放。
7. 查询线程信息
查询线程信息:GetExitCodeThread函数可以查询线程的退出代码。NtQueryInformationThread(非官方文档)可以查询更多的线程相关信息,但使用时需谨慎,因为这些API不是公开文档的一部分,可能会在不同版本的Windows上发生变化。

       

注意事项

  • 资源管理:在使用线程句柄进行各种操作后,一定要记得使用CloseHandle函数关闭句柄,以避免资源泄露。
  • 线程安全:在多线程编程中,对共享资源的访问需要特别注意线程安全问题,避免数据竞争和死锁。
  • 性能考虑:改变线程优先级和频繁地暂停/恢复线程可能会影响应用程序的性能和响应能力。
  • 终止线程TerminateThread虽然可以立即终止线程,但不推荐使用,因为它可能导致资源泄露、死锁以及不一致的全局状态。

5. 获取某进程内的线程信息

#include <windows.h>
#include <tlhelp32.h>
#include <iostream>void PrintThreadsOfProcess(DWORD processID) {THREADENTRY32 te32;HANDLE hThreadSnap = INVALID_HANDLE_VALUE;// 创建线程快照hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);if (hThreadSnap == INVALID_HANDLE_VALUE) {std::cerr << "CreateToolhelp32Snapshot failed: " << GetLastError() << std::endl;return;}// 初始化THREADENTRY32结构体的大小te32.dwSize = sizeof(THREADENTRY32);// 遍历快照中的第一个线程,开始遍历if (!Thread32First(hThreadSnap, &te32)) {std::cerr << "Thread32First failed: " << GetLastError() << std::endl; // 显示失败信息CloseHandle(hThreadSnap);          // 关闭快照句柄return;}// 继续遍历快照中剩余的线程,并打印属于特定进程ID的线程信息do {if (te32.th32OwnerProcessID == processID) {std::cout << "Thread ID: " << te32.th32ThreadID << std::endl;}} while (Thread32Next(hThreadSnap, &te32));CloseHandle(hThreadSnap); // 关闭快照句柄
}int main() {DWORD myProcessID; // 请替换为你感兴趣的进程IDstd::cout << "Enter the Process ID: ";std::cin >> myProcessID;if (myProcessID > 0) {PrintThreadsOfProcess(myProcessID);} else {std::cout << "Invalid Process ID." << std::endl;}return 0;
}

        需要知道的是:无论 CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);的第二个参数是什么,它遍历的都是整个系统的线程信息。第二个参数对于TH32CS_SNAPMODULETH32CS_SNAPHEAPLIST有作用。

进程遍历模板代码:

#include <windows.h>
#include <tlhelp32.h>
#include <iostream>// 函数声明:遍历所有进程并打印信息
void EnumerateProcesses() {// 创建系统快照HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (hSnapshot == INVALID_HANDLE_VALUE) {std::wcerr << L"CreateToolhelp32Snapshot failed." << std::endl;return;}PROCESSENTRY32W pe32;   //pe32中有一些信息可供使用者参考,在这加个关键判断pe32.dwSize = sizeof(PROCESSENTRY32W);// 获取快照中的第一个进程if (!Process32FirstW(hSnapshot, &pe32)) {std::wcerr << L"Process32First failed." << std::endl;CloseHandle(hSnapshot);return;}// 遍历所有进程do {// 输出进程的ID和名称(使用宽字符)// 此处可以用ID打开此进程详细信息,OpenProcessstd::wcout << L"Process ID: " << pe32.th32ProcessID << L"\tProcess Name: " << pe32.szExeFile << std::endl;} while (Process32NextW(hSnapshot, &pe32));CloseHandle(hSnapshot);
}int main() {EnumerateProcesses();return 0;
}

 

线程遍历模板代码,需要指定进程ID来筛选此进程下的线程。


void PrintThreadsOfProcess(DWORD processID) {THREADENTRY32 te32;HANDLE hThreadSnap = INVALID_HANDLE_VALUE;// 创建线程快照hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);if (hThreadSnap == INVALID_HANDLE_VALUE) {std::cerr << "CreateToolhelp32Snapshot failed: " << GetLastError() << std::endl;return;}// 初始化THREADENTRY32结构体的大小te32.dwSize = sizeof(THREADENTRY32);// 遍历快照中的第一个线程,开始遍历if (!Thread32First(hThreadSnap, &te32)) {std::cerr << "Thread32First failed: " << GetLastError() << std::endl; // 显示失败信息CloseHandle(hThreadSnap);          // 关闭快照句柄return;}// 继续遍历快照中剩余的线程,并打印属于特定进程ID的线程信息do {if (te32.th32OwnerProcessID == processID) {std::cout << "Thread ID: " << te32.th32ThreadID << std::endl;}} while (Thread32Next(hThreadSnap, &te32));CloseHandle(hThreadSnap); // 关闭快照句柄
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/788687.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20240402—Qt如何通过动态属性设置按钮样式?

前言 正文 1、点击UI文件 2、选择Bool型或是QString 3、设置后这里出现动态属性 4、这qss文件中绑定该动态属性 QPushButton[PopBlueBtn"PopBlueBtn"]{background-color:#1050B7;color:#FFFFFF;font-size:20px;font-family:Source Han Sans CN;//思源黑体 CNbor…

【JavaEE初阶系列】——一万字带你了解 JUC常见类 以及 线程安全集合类(哈希表)

目录 &#x1f6a9;JUC(java.util.concurrent) 的常见类 &#x1f388;Callable 接口 &#x1f308;理解 Callable(相关面试题) &#x1f308;理解 FutureTask &#x1f4dd;线程创建方式 &#x1f388; ReentrantLock可重入锁 &#x1f308;ReentrantLock 优势&#x…

Codeforces Round 798 (Div. 2) (C 树形dp D 曼哈顿距离转换 E 位运算构造)

C&#xff1a;dp以f[i]为根的时候能获得多少个节点&#xff0c;那么dp就是全部儿子里面找一个切掉&#xff0c;其他就是 f[v]的总和了 #include<bits/stdc.h> using namespace std; const int N 1e610,M2*N,mod1e97; #define int long long typedef long long LL; typ…

4.2日java总结,以及窗口的创建

今日份学习——字符串的进阶 1.StringBulider StringBulider是一个java里的关键字&#xff0c;可以看做一个容器&#xff0c;但是其是一个可以改变的容器&#xff0c;对其有四种操作可以进行&#xff0c;分别是添加元素&#xff08;append&#xff09;&#xff0c;反转元素&a…

谷粒商城实战(009 缓存-分布式锁)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第158p-第p165的内容 分布式锁 原理和使用 使用下shell对产生的命令进行发送 查看 -> 撰写 -> 撰写栏 idea 选中的代码提取成方法 加锁…

【教学类-09-07】20240401细线迷宫图02+箭头图片(A4横版一页-2份竖版)

作品展示 作品展示 word模板 重点说明 代码展示 批量制作细线条的迷宫图(A4横板一面2张竖版)箭头图片 作者&#xff1a; 1、落难Coder https://blog.csdn.net/u014297502/article/details/124839912 2、AI对话大师 3、阿夏 作者&#xff1a;2024年4月3日 numint(input(几人&…

Android14之BpBinder构造函数Handle拆解(二百零四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Docker:探索容器化技术,重塑云计算时代应用交付与管理

一&#xff0c;引言 在云计算时代&#xff0c;随着开发者逐步将应用迁移至云端以减轻硬件管理负担&#xff0c;软件配置与环境一致性问题日益凸显。Docker的横空出世&#xff0c;恰好为软件开发者带来了全新的解决方案&#xff0c;它革新了软件的打包、分发和管理方式&#xff…

AIGC之gradio系列学习教程(一)

主题:本篇即为入门,后续将会从函数和使用场景介绍。 Gradio 是一个开源 Python 包,可让快速为机器学习模型、API 或任何任意 Python 函数构建演示或 Web 应用程序。然后,您可以使用 Gradio 的内置共享功能在短短几秒钟内共享演示或 Web 应用程序的链接。无需 JavaScript、…

【layui-table】转静态表格时固定表格列处理行高和单元格颜色

处理思路&#xff1a;覆盖layui部分表格样式 行高处理&#xff1a;获取当前行数据单元格的最高高度&#xff0c;将当前行所有数据单元格高度设置为该最高高度 单元格颜色处理&#xff1a;将原生表格转换为layui表格后&#xff0c;因为原生表格的表格结构和生成的layui表格结构…

C#中值类型与引用类型的存储

目录 值对象与引用对象的存储 引用对象的成员存储 值对象与引用对象的存储 数据项的类型定义了存储数据需要的内存大小及组成该类型的数据成员。类型还决定了对象在内存中的存储位置——栈或堆。 C#中类型分为两种&#xff1a;值类型和引用类型&#xff0c;这两种类型的对象…

中介者模式:优雅解耦的利器

在软件设计中&#xff0c;随着系统功能的不断扩展&#xff0c;对象之间的依赖关系往往会变得错综复杂&#xff0c;导致系统难以维护和扩展。为了降低对象之间的耦合度&#xff0c;提高系统的可维护性和可扩展性&#xff0c;设计模式应运而生。中介者模式&#xff08;Mediator P…

【蓝桥杯 C++高级组省赛以及2020年-蓝桥杯C++省赛合集+部分答案】

一、选择题&#xff08;单项选择&#xff0c;每空30分&#xff09; 请将选择题答案填入答题卡蓝色框内 第一题&#xff08;难度系数 1&#xff09; 结构化程序所要求的基本结构不包括( )。 A.顺序结构 B.GOTO()跳转 C.选择(分支)结构 D.重复(循环)结构 第二题&#xff…

记录一下做工厂的打印pdf程序

功能&#xff1a;在网页点击按钮调起本地的打印程序 本人想到的就是直接调起方式&#xff0c;网上大佬们说用注册表的形式来进行。 后面想到一种&#xff0c;在电脑开机时就开启&#xff0c;并在后台运行&#xff0c;等到有人去网页里面进行触发&#xff0c;这时候就有个问题&a…

银行监管报送系统介绍(十五):金融审计平台

《“十四五”国家审计工作发展规划》中重点强调&#xff0c;金融审计&#xff1a;以防范化解重大风险、促进金融服务实体经济&#xff0c;推动深化金融供给侧结构性改革、建立安全高效的现代金融体系为目标&#xff0c;加强对金融监管部门、金融机构和金融市场运行的审计。 —…

面试题:MySQL 事务 日志 MVCC

事务的特性 ACID 事务的隔离级别 并发事务问题 脏读&#xff1a;一个事务读到另一个事务还没有提交的数据不可重复读&#xff1a;一个事务先后读取同一条记录&#xff0c;但两次读取的数据不同幻读&#xff1a;一个事务按照条件查询数据时&#xff0c;没有对应的数据行&#xf…

Oracle EBS AR接口和OM销售订单单价为空数据修复

最近,用户使用客制化Web ADI 批量导入销售订单行功能,把销售订单行的单价更新成空值,直到发运确认以后,财务与客户对帐才发现大量销售订单的单价空,同时我们检查AR接口发现销售订单的单价和金额均为空。 前提条件 采用PAC成本方式具体问题症状 销售订单行的单价为空 Path:…

Redhat 7.9 安装dm8配置文档

Redhat 7.9 安装dm8配置文档 一 创建用户 groupadd -g 12349 dinstall useradd -u 12345 -g dinstall -m -d /home/dmdba -s /bin/bash dmdba passwd dmdba二 创建目录 mkdir /dm8 chown -R dmdba:dinstall /dm8三 配置/etc/security/limits.conf dmdba soft nproc 163…

文心一言 vs GPT-4 —— 全面横向比较

对于文心一言和 GPT-4 这两者之间的全面横向比较&#xff0c;我们可以从多个方面来看待它们的区别和优劣势。 文心一言 文心一言是一款基于深度学习的中文文本生成模型&#xff0c;专注于生成优美的古风诗句和语录。以下是它的一些特点&#xff1a; 专注于古风诗句和语录: 文…

在CentOS 7上安装Python 3.7.7

文章目录 一、实战步骤1. 安装编译工具2. 下载Python 3.7.7安装包3. 上传Python 3.7.7安装包4. 解压缩安装包5. 切换目录并编译安装6. 配置Python环境变量7. 使配置生效8. 验证安装是否成功 二、实战总结 一、实战步骤 1. 安装编译工具 在终端中执行以下命令 yum -y groupin…