在与Chat GPT和Bing聊天进行了一晚的辩论后,我现在转向Stackoverflow的(希望)更聪明的人.我的问题是:我有用C#实现的低级钩子.我在PowerShell类中订阅事件,然后在图形用户界面中使用它.订阅事件并启动钩子工作得很好,但是,我不能从我的类或图形用户界面程序中停止钩子.现在我在想,只要我按下‘f4’,钩子就应该停止,但我一直收到‘无法在空值表达式上调用方法’的消息,我真的不明白为什么它会是空值的,或者我该如何解决它.下面是我的代码,我认为没有必要显示钩子或图形用户界面的实现,但请让我知道.

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()
    }
}

推荐答案

problem路就是method-local 100 variable isn't available from inside the script block serving as an event delegate passed to the 101 method路.

预计PowerShell的Dynamic scoping也适用于PowerShell的定制classES,这是可以理解的,但情况是not:

script block作为event delegate传递给.NET方法from inside a class:

  • not是否看到封闭的method的局部变量--除非您通过在脚本块上调用.GetNewClosure()来显式捕获它们.

    • 在没有.GetNewClosure()个的情况下,它does看到的是class-defining scope中的变量,即类中定义outside的变量,在定义类的作用域中(以及从该作用域的祖先作用域中),因为它在that个作用域的孙子作用域中运行.
  • not是否认为$this指的是instance of the enclosing class,因为--不幸的是--事件中的automatic $this variable代表$thisclass级定义,而不是指event sender.

    • 因为类内部使用$this是访问类的instance variables(属性)的唯一方法,所以后者也被隐藏了.

有两个solution options:

  • On a per-method basis(使您的try 奏效):

    • 定义一个方法局部变量(如$self),然后对传递给.NET方法from the same method的每个事件委托脚本块调用.GetNewClosure(),这允许您访问该脚本块中的方法局部变量.

    • 但是,请注意,.GetNewClosure()将使您的脚本块失go 对类定义作用域中变量的访问.

  • Preferably, at the class level(无需特定于方法的代码):

    • 使用(Get-Variable -Scope 1 -ValueOnly this)可以访问手边的类实例,即访问$thisshadowed版本

    • 这消除了对特定于方法的逻辑的需要,还保留了对类定义作用域中的变量的访问(如果需要).


以下self-contained sample code个示例演示了这两种方法:

  • 为了便于可视化,创建了一个WinForms表单(作用域问题同样适用),其中包含两个按钮,它们的事件处理程序演示了上面的任何一种方法.

  • 单击任一按钮都会使用不同的文本更新表单的标题文本,这样做意味着从事件处理程序脚本块内部成功获得了对封闭class实例的引用.

Important:

  • 确保执行以下firstbefore代码,通过脚本文件调用以下代码:

    Add-Type -ErrorAction Stop -AssemblyName System.Windows.Forms
    
  • 不幸的是,这是必要的,因为(至少在PowerShell7.4.0之前)class定义中引用的任何.NET类型必须已经加载到会话before中.脚本被解析(加载)-有关背景信息,请参见this answer.

# 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)似乎没有method级作用域(因此无法访问方法局部变量)和(B)在实例级上定义的唯一variable$this,即shadowed,如后面所述(并且任何instance variables(属性)都必须通过$this.访问)

Csharp相关问答推荐

C#中的包版本控制

Blazor:用参数创建根路径

错误NU 1301:无法加载源的服务索引

CS0103 dlibdotnet和www.example.com facerect不在上下文中

MudBlazor—MudDataGrid—默认过滤器定义不允许用户修改基本过滤器

try 在Blazor项目中生成html

使用Orleans进行的单元测试找不到接口的实现

在调整大小的控件上绘制

我想在文本框悬停时在其底部显示一条线

如何将端点(或с匹配请求并判断其路径)添加到BCL?

如何在Akka.NET中重新启动执行元时清除邮箱

使用ASP.NET MVC for Lemon Squeezy X-Signature创建散列

.NET8Blazor-为什么Rapzor渲染在for循环之后显示?

毛伊岛.NET 8图片不再适合按钮

如何消除Visual Studio错误,因为它不识别集合表达式的新C#12语法?

如何正确地在VB中初始化类?

在C#中通过Matheval使用自定义公式

无法使用直接URL通过PictureBox.ImageLocation加载图像

实例化列表时的集合表达式是什么?

与Visual Studio 2022中的.NET框架相比,如何在.NET Core 6中获取错误输出的窗口句柄