提问人:pfeidewigo100 提问时间:11/4/2023 更新时间:11/5/2023 访问量:75
无法停止 powershell 中的低级别挂钩(c# 挂钩)
Can't stop low level hooks in powershell (c# hook)
问:
在与 Chat GPT 和 Bing Chat 争论了一个晚上之后,我现在转向 stackoverflow 的(希望)更聪明的人。我的问题是:我在 c# 中实现了低级钩子。我订阅 powershell 类中的事件,然后在 GUI 中使用这些事件。订阅事件并启动钩子工作得很好,但是,我无法从我的类或 GUI 程序中停止钩子。目前我在想,每当我按下“f4”时,钩子都应该停止,但我总是收到“无法在空值表达式上调用方法”,我真的不明白为什么它会是空值或我将如何解决它。这是我的代码,我认为没有必要展示钩子或 GUI 的实现,但请告诉我其他情况。
class InputRecorder {
[KeyboardHookExample.KeyboardHook] $kh
[Ikst.MouseHook.MouseHook] $mh
[System.Windows.Forms.ListView] $list
InputRecorder() {
$this.kh = New-Object KeyboardHookExample.KeyboardHook
$this.mh = New-Object Ikst.MouseHook.MouseHook
# Store the reference to the class instance
$self = $this
# Define the event handler for KeyDown
$self.kh.add_KeyDown({
param($sender, $e)
$vkCode = $sender.vkCode
Write-Host $vkCode
if ($vkCode -eq 115) {
$self.kh.Stop()
$self.mh.Stop()
}
$charCode = [Win32.NativeMethods]::MapVirtualKey($vkCode, 2)
$char = [char]$charCode
Write-Host $char
})
# Define the event handler for LeftButtonDown
$self.mh.add_LeftButtonDown({
param($sender, $e)
$mousePosition = $sender.pt
$y = $mousePosition.y
$x = $mousePosition.x
$item = New-Object System.Windows.Forms.ListViewItem
$item.ToolTipText = $global:dict["LeftClickCode"] -f $x, $y
$item.Text = $global:dict["LeftClickDescription"] -f $x, $y
$CMDList.Items.Add($item)
})
# Start the keyboard and mouse hooks
$self.kh.Start()
$self.mh.Start()
}
[System.Collections.Concurrent.ConcurrentBag[string]] getList() {
return $this.list
}
[void] StopHooks() {
$this.mh.Stop()
$this.kh.Stop()
}
}
答:
问题在于方法局部$self
变量在脚本块内部不可用,用作传递给 .add_KeyDown()
方法的事件委托。
虽然期望 PowerShell 的动态范围也应用于 PowerShell 的自定义类
是可以理解的,但事实并非如此:
作为事件委托从类内部传递给 .NET 方法的脚本块:
看不到 enclosing 方法的局部变量 - 除非您通过调用 显式捕获它们
。GetNewClosure()
在脚本块上。- 在没有的情况下,它所看到的是来自类定义作用域的变量,即在类外部定义的变量,在定义类的作用域中(以及来自该作用域的祖先作用域),因为它在该作用域的孙子作用域中运行。[1]
.GetNewClosure()
- 在没有的情况下,它所看到的是来自类定义作用域的变量,即在类外部定义的变量,在定义类的作用域中(以及来自该作用域的祖先作用域),因为它在该作用域的孙子作用域中运行。[1]
不认为它指的是封闭类的实例,因为 - 不幸的是 - 事件委托中的自动
$this
变量隐藏了 -level 定义,而是引用了事件发送方。$this
class
$this
- 而且,由于类内部使用 of 是访问类的实例变量(属性)的唯一方法,因此后者也会被遮蔽。
$this
- 而且,由于类内部使用 of 是访问类的实例变量(属性)的唯一方法,因此后者也会被遮蔽。
有两种解决方案选项:
在每种方法的基础上(使您的尝试成功):
定义一个方法局部变量,例如,然后调用从同一方法传递给 .NET 方法的每个事件委托脚本块,这允许您访问该脚本块中的方法局部变量。
$self
.GetNewClosure()
但是,请注意,这将使脚本块无法访问类定义作用域中的变量。
.GetNewClosure()
最好是在类级别(不需要特定于方法的代码):
用于访问手头的类实例,即
(Get-Variable -Scope 1 -ValueOnly this)
$this
这样就不需要特定于方法的逻辑,并且还保留了对类定义作用域中变量的访问(如果需要)。
以下独立示例代码演示了这两种方法:
为了便于可视化,创建了一个 WinForms 表单(范围问题同样适用),其中包含两个按钮,其事件处理程序演示了上述任一方法。
单击任一按钮都会使用不同的文本更新窗体的标题文本,并且这样做的能力意味着已从事件处理程序脚本块内部成功获取了对封闭实例的引用。
class
重要:
在通过脚本文件调用以下代码之前,请务必先执行以下命令:
Add-Type -ErrorAction Stop -AssemblyName System.Windows.Forms
不幸的是,这是必要的,因为(至少 PowerShell 7.4.0)定义中引用的任何 .NET 类型都必须在分析(加载)脚本之前加载到会话中 - 有关背景信息,请参阅此答案。
class
# IMPORTANT:
# * Run
# Add-Type -AssemblyName System.Windows.Forms
# BEFORE invoking this script.
using namespace System.Windows.Forms
using namespace System.Drawing
class FormWrapper {
[Form] $form
FormWrapper() {
# Create a form with two buttons that update the form's caption (title text).
$this.form = [Form] @{ Text = "Sample"; Size = [Size]::new(360, 90); StartPosition = 'CenterScreen' }
$this.form.Controls.AddRange(@(
[Button] @{
Name = 'button1'
Location = [Point]::new(30, 10); Size = [Size]::new(130, 30)
Text = "With Get-Variable"
}
[Button] @{
Name = 'button2'
Location = [Point]::new(190, 10); Size = [Size]::new(130, 30)
Text = "With GetNewClosure"
}
))
# Get-Variable approach.
$this.form.Controls['button1'].add_Click({
param($sender, [System.EventArgs] $evtArgs)
# Obtain the shadowed $this variable value to refer to
# this class instance and access its $form instance variable (property)
(Get-Variable -Scope 1 -ValueOnly this).form.Text = 'Button clicked (Get-Variable)'
})
# Local variable + .GetNewClosure() approach.
# Define a method-local $self variable to cache the value of $this.
$self = $this
# !! YOU MUST CALL .GetNewClosure() on the event-delegate script
# !! block to capture the local $self variable
$this.form.Controls['button2'].add_Click({
param($sender, [System.EventArgs] $evtArgs)
# Use the captured local $self variable to refer to this class instance.
$self.form.Text = 'Button clicked (.GetNewClosure)'
}.GetNewClosure())
}
ShowDialog() {
# Display the dialog modally
$null = $this.form.ShowDialog()
}
}
# Instantiate the class and show the form.
[FormWrapper]::new().ShowDialog()
[1] 直接父作用域是手头的类实例,但 (a) 似乎没有方法级作用域(因此无法访问方法局部变量)和 (b) 在实例级别,唯一定义的变量是 $this,它是隐藏的,如后面所述(任何实例变量(属性)都必须通过$this
访问。
评论
Get-Variable
评论