win_wrap/threading.rs
1/*
2 * Copyright (c) 2024. The RigelA open source project team and
3 * its contributors reserve all rights.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and limitations under the License.
12 */
13
14use crate::{
15 common::{close_handle, Result, HANDLE, HWND, LPARAM, WAIT_EVENT, WPARAM},
16 message::post_thread_message,
17};
18pub use windows::Win32::{
19 Security::SECURITY_ATTRIBUTES,
20 System::Threading::{
21 PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_READ_CONTROL,
22 PROCESS_SET_INFORMATION, PROCESS_SET_LIMITED_INFORMATION, PROCESS_SET_QUOTA,
23 PROCESS_SET_SESSIONID, PROCESS_STANDARD_RIGHTS_REQUIRED, PROCESS_SUSPEND_RESUME,
24 PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, PROCESS_VM_OPERATION, PROCESS_VM_READ,
25 PROCESS_VM_WRITE, PROCESS_WRITE_DAC, PROCESS_WRITE_OWNER,
26 },
27};
28use windows::{
29 core::HSTRING,
30 Win32::{
31 System::Threading::{
32 CreateEventW, GetCurrentProcessId, GetCurrentThreadId, GetProcessHandleFromHwnd,
33 OpenProcess, SetEvent, WaitForSingleObject, PROCESS_ACCESS_RIGHTS,
34 },
35 UI::WindowsAndMessaging::{GetWindowThreadProcessId, WM_QUIT},
36 },
37};
38
39//noinspection SpellCheckingInspection
40/**
41从窗口句柄查询进程句柄。
42如果成功,则返回拥有窗口的进程句柄。
43如果未成功,则返回 NULL。
44在以前版本的操作系统中,进程可以打开另一个进程 (访问其内存,例如使用 open_process) 。 如果调用方具有适当的权限,则此函数成功;通常调用方和目标进程必须是同一用户。
45但是,在 Windows Vista 上, open_process 在调用方具有 UIAccess 的情况下失败,并且目标进程提升。 在这种情况下,目标进程的所有者位于 Administrators 组中,但调用进程使用受限令牌运行,因此此组中没有成员身份,并拒绝访问提升的进程。 但是,如果调用方具有 UIAccess,则可以使用 Windows 挂钩将代码注入目标进程,并从目标进程中将句柄发送回调用方。
46get_process_handle_from_hwnd 是一个便捷函数,它使用此技术获取拥有指定 HWND 的进程句柄。 请注意,仅当调用方和目标进程以同一用户身份运行时,它才会成功。 返回的句柄具有以下特权:PROCESS_DUP_HANDLE |PROCESS_VM_OPERATION |PROCESS_VM_READ |PROCESS_VM_WRITE | PROCESS_SYNCHRONIZE。 如果需要其他权限,可能需要显式实现挂钩技术,而不是使用此函数。
47`h_wnd` 窗口句柄。
48*/
49pub fn get_process_handle_from_hwnd(h_wnd: HWND) -> HANDLE {
50 unsafe { GetProcessHandleFromHwnd(h_wnd) }
51}
52/**
53查询调用线程的线程标识符。
54*/
55pub fn get_current_thread_id() -> u32 {
56 unsafe { GetCurrentThreadId() }
57}
58
59/**
60查询调用进程的进程标识符。
61*/
62pub fn get_current_process_id() -> u32 {
63 unsafe { GetCurrentProcessId() }
64}
65
66//noinspection StructuralWrap
67/**
68查询创建指定窗口的线程的标识符,以及创建该窗口的进程的标识符。
69`h_wnd` 窗口的句柄。
70*/
71pub fn get_window_thread_process_id(h_wnd: HWND) -> (u32, u32) {
72 let mut pid = 0u32;
73 (
74 unsafe { GetWindowThreadProcessId(h_wnd, Some(&mut pid)) },
75 pid,
76 )
77}
78
79/**
80建立或开启具名或未命名的事件对象。
81若要指定对象的访问屏蔽,请使用 create_event_ex 函数。
82`event_attributes` SECURITY_ATTRIBUTES结构。如果此参数为 Null,子进程就无法继承句柄。结构的 lpSecurityDescriptor 成员会指定新事件的安全性描述符。如果 lpEventAttributes 为 Null,事件会取得默认的安全性描述符。事件的默认安全性描述符中的 ACL 来自创建者的主要或模拟权限。
83`manual_reset` 如果此参数为TRUE,此函数会建立手动重置事件对象,这需要使用 reset_event 函数将事件状态设定为非signaled。如果此参数为 FALSE,此函数会建立自动重置事件对象,而且系统会在释放单一等候线程之后,自动将事件状态重设为非signaled。
84`initial_state` 如果此参数为TRUE,则会发出事件对象的初始状态信号;否则,则为非signaled。
85`name` 事件对象的名称。名称限制为 MAX_PATH个字符。名称比较区分大小写。如果 name 符合现有具名事件对象的名称,此函式会要求 EVENT_ALL_ACCESS 访问权限。在此情况下,系统会忽略 manual_reset 和 initial_state 参数,因为它们已经由建立进程设定。如果 event_attributes 参数不是 Null,它会判断是否可以继承句柄,但会忽略其安全性描述符成员。如果 name 为 Null,则会建立事件对象,而不需要名称。如果 name 符合相同命名空间中另一种对象的名称,(例如现有的号志、mutex、可等候计时器、作业或文件对应对象),则函数会失败,而且 get_last_error 函数会传回 ERROR_INVALID_HANDLE。这是因为这些对象共享相同的命名空间。名称可以有 Global 或 「Local」 前置词,以在全局或会话命名空间中明确建立对象。名称的其余部分可以包含反斜线字符(\)以外的任何字符。使用终端机服务会话实作快速用户切换。核心对象名称必须遵循终端机服务概述的指导方针,让应用程序可以支持多个用户。对象可以在私用命名空间中建立。
86*/
87pub fn create_event(
88 event_attributes: Option<*const SECURITY_ATTRIBUTES>,
89 manual_reset: bool,
90 initial_state: bool,
91 name: Option<&str>,
92) -> HANDLE {
93 unsafe {
94 match name {
95 None => CreateEventW(event_attributes, manual_reset, initial_state, None),
96 Some(x) => CreateEventW(
97 event_attributes,
98 manual_reset,
99 initial_state,
100 &HSTRING::from(x),
101 ),
102 }
103 }
104 .expect("Can't create the event.")
105}
106
107/**
108设置事件的状态为signaled,释放任意等待线程。如果事件是手工的,此事件将保持signaled直到调用reset_event,这种情况下将释放多个线程;如果事件是自动的,此事件将保持signaled,直到一个线程被释放,系统将设置事件的状态为非signaled;如果没有线程在等待,则此事件将保持signaled,直到一个线程被释放。
109`h_event` 事件句柄。
110*/
111pub fn set_event(h_event: HANDLE) {
112 unsafe { SetEvent(h_event) }.expect("Can't set the event.")
113}
114
115/**
116等待指定的对象处于信号状态或超时间隔已过。若要进入可警报等待状态,请使用 wait_for_single_object_ex 函数。若要等待多个对象,请使用 wait_for_multiple_objects。
117`h_handle` 对象的句柄。如果在等待仍处于挂起状态时关闭此句柄,则函数的行为未定义。句柄必须具有 SYNCHRONIZE 访问权限。
118`milliseconds` 超时间隔(以毫秒为单位)。如果指定了非零值,则函数将等待,直到发出对象信号或间隔已过。如果 milliseconds 为零,则如果未向对象发出信号,则函数不会进入等待状态;它始终立即返回。如果 milliseconds 为 INFINITE,则函数仅在发出对象信号时返回。Windows XP、Windows Server 2003、Windows Vista、Windows 7、Windows Server 2008 和 Windows Server 2008 R2:milliseconds 值包括在低功率状态下花费的时间。例如,当计算机处于睡眠状态时,超时也会持续倒计时。Windows 8、Windows Server 2012、Windows 8.1、Windows Server 2012 R2、Windows 10和Windows Server 2016:milliseconds 值不包括在低功率状态下花费的时间。例如,当计算机处于睡眠状态时,超时会暂停倒计时。
119*/
120pub fn wait_for_single_object(h_handle: HANDLE, milliseconds: u32) -> WAIT_EVENT {
121 unsafe { WaitForSingleObject(h_handle, milliseconds) }
122}
123
124/** 在线程之间发送通知事件。 */
125#[derive(Clone)]
126pub struct ThreadNotify(u32, HANDLE);
127
128unsafe impl Send for ThreadNotify {}
129unsafe impl Sync for ThreadNotify {}
130
131impl ThreadNotify {
132 /**
133 创建一个通知对象。
134 `thread_id` 一个线程ID,此线程必须有一个消息循环。
135 */
136 pub fn new(thread_id: u32) -> Self {
137 let event = create_event(None, true, false, None);
138 Self(thread_id, event)
139 }
140
141 /**
142 往线程的消息队列中发送一个退出请求,然后此函数会立即返回。
143 */
144 pub fn quit(&self) {
145 post_thread_message(self.0, WM_QUIT, WPARAM::default(), LPARAM::default());
146 }
147
148 /**
149 等待finish函数被调用,一个应用场景就是主线程需要等待子线程是否接收到退出信号并调用了finish方法,当子线程调用finish方法时,join会返回。
150 `millis` 等待的超时时间(毫秒),如果在指定的时间内子线程没有调用过finish,join也会返回。
151 */
152 pub fn join(&self, millis: u32) {
153 wait_for_single_object(self.1, millis);
154 close_handle(self.1);
155 }
156
157 /**
158 通知join方法返回,这通常放在线程结束时调用。
159 */
160 pub fn finish(&self) {
161 set_event(self.1);
162 }
163}
164
165//noinspection SpellCheckingInspection
166/**
167打开现有的本地进程对象。
168`desired_access` 对进程对象的访问。 根据进程的安全描述符检查此访问权限。 此参数可以是一个或多个 进程访问权限。如果调用方已启用 SeDebugPrivilege 特权,则无论安全描述符的内容如何,都会授予请求的访问权限。
169`inherit_handle` 如果此值为 TRUE,则此进程创建的进程将继承句柄。 否则,进程不会继承此句柄。
170`process_id` 要打开的本地进程的标识符。如果指定的进程是系统空闲进程(0x00000000),则函数将失败,最后一个错误代码为ERROR_INVALID_PARAMETER。如果指定的进程是系统进程或客户端服务器Run-Time子系统(CSRSS)进程之一,则此函数将失败,最后一个错误代码是ERROR_ACCESS_DENIED因为它们的访问限制阻止用户级代码打开它们。如果使用get_current_process_id作为此函数的参数,请考虑使用get_current_process而不是open_process来提高性能。
171*/
172pub fn open_process(
173 desired_access: PROCESS_ACCESS_RIGHTS,
174 inherit_handle: bool,
175 process_id: u32,
176) -> Result<HANDLE> {
177 unsafe { OpenProcess(desired_access, inherit_handle, process_id) }
178}