添加类型变体:-MemberType 与 -TypeDefinition 参数

Add-Type variations: -MemberType vs. -TypeDefinition parameters

谁能解释一下这种方法与 Add-Type 的区别

$definition = [Text.StringBuilder]"" 
    [void]$definition.AppendLine('[DllImport("user32.dll")]') 
    [void]$definition.AppendLine('public static extern int LoadString(IntPtr h, uint id, System.Text.StringBuilder sb, int maxBuffer);') 
    [void]$definition.AppendLine('[DllImport("kernel32.dll")]') 
    [void]$definition.AppendLine('public static extern IntPtr LoadLibrary(string s);') 
    Add-Type -memberDefinition:$definition.ToString() -name:Utility -namespace:PxTools

还有这样的东西

Add-Type -typeDefinition @"
public class BasicTest
{
  public static int Add(int a, int b)
    {
        return (a + b);
    }
  public int Multiply(int a, int b)
    {
    return (a * b);
    }
}
"@

我经常看到后者的例子,但前者我只在一些示例代码中看到过固定到任务栏。这只是给猫剥皮的两种不同方法,还是在某些用例中需要前者? 而且,如果两者始终有效,将后一种方法与前一种方法一起使用会是什么样子?

编辑:我考虑过将其作为一个新线程,但在我看来这是对原始问题的扩展,所以希望这是正确的方法。

我已经根据从 this post...

中学到的知识实现了代码
$targetFile = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Snipping Tool.lnk"
$action = 'PinToTaskbar'

$verbs = @{  
    'PinToStartMenu' = 5381 
    'UnpinFromStartMenu' = 5382 
    'PinToTaskbar' = 5386 
    'UnpinFromTaskbar' = 5387
}

try { 
    $type = [type]"PxTools.Utility" 
}  catch { 
    $definition = [Text.StringBuilder]"" 
    [void]$definition.AppendLine('[DllImport("user32.dll")]') 
    [void]$definition.AppendLine('public static extern int LoadString(IntPtr h, uint id, System.Text.StringBuilder sb, int maxBuffer);') 
    [void]$definition.AppendLine('[DllImport("kernel32.dll")]') 
    [void]$definition.AppendLine('public static extern IntPtr LoadLibrary(string s);') 
    Add-Type -memberDefinition:$definition.ToString() -name:Utility -namespace:PxTools          
} 
if ($script:Shell32 -eq $null) {         
    $script:Shell32 = [PxTools.Utility]::LoadLibrary("shell32.dll") 
} 
$maxVerbLength = 255 
$verb = new-object Text.StringBuilder "", $maxVerbLength 
[void][PxTools.Utility]::LoadString($script:Shell32, $verbs.$action, $verb, $maxVerbLength) 
$verbAsString = $verb.ToString()
try {
    $path = Split-Path $targetFile -parent -errorAction:stop
    $file = Split-Path $targetFile -leaf -errorAction:stop
    $shell = New-Object -com:"Shell.Application" -errorAction:stop 
    $folder = $shell.Namespace($path)    
    $target = $($folder.Parsename($file)).Verbs() | Where-Object {$_.Name -eq $verbAsString}
    $target.DoIt()
    Write-Host "$($action): $file"
} catch {
    Write-Host "Error managing shortcut"
}

现在我有三个关于重构的问题。

1:如何重构 Add-Type 以使用 Here-String? 编辑:这似乎可行,所以我将修改问题,这是 best/most 优雅的解决方案,还是可以改进?

Add-Type -memberDefinition:@"
    [DllImport("user32.dll")]
    public static extern int LoadString(IntPtr h, uint id, System.Text.StringBuilder sb, int maxBuffer);
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string s);
"@  -name:Utility -namespace:PxTools

2:Test.StringBuilder 既用作 $definition 字符串的类型,也用于 LoadString 方法。这是必需的,还是可以在根本不使用 StringBuilder 的情况下实现? 编辑:我在上面的重构中消除了 SB 作为数据类型,但不确定是否在 LoadString 中这样做。可能是因为在更改代码之前,我仍在努力弄清楚代码的作用。

3:$script:Shell32位是否需要这样处理,还是可以不滚入Type?而且,原来的全局作用域(我改成了脚本作用域)真的有必要吗?在我看来,除非我这样做数百次多次调用 LoadLibrary 不会有什么大不了的?

  • 回复 1:

是的,以这种方式使用此处字符串是最好的方法。

附带说明一下,使用 : 将参数名称与其值分开是可行的,但并不常见;通常,使用 space(例如,-name Utility 而不是 -name:Utility)。

  • 回复 2:

没有充分的理由在类型定义中使用 [System.Text.StringBuilder]
使用此处字符串、常规字符串或字符串数​​组。
正如您所展示的,除了在 Windows API 调用中使用之外,您考虑在 PowerShell 中使用 [System.Text.StringBuilder] 的唯一原因是性能至关重要并且您需要建立一个非常来自动态创建的片段的大字符串。

Gordon 自己指出,在 LoadString() Windows API 函数的 sb 参数中使用 [System.Text.StringBuilder] 是必要的,因为它是一个 out 参数 接收 一个字符串,而 [string] 类型是 immutable .

  • 回复 3:

可以结合这两种方法 - P/Invoke 签名 [DllImport(... 一方面 -MemberType 和自定义类型定义 (class BasicTest ...) 与 -TypeDefinition 在另一个方面(有关这两种方法的背景信息,请参阅此 post 的底部)。

关于script范围的旁注:script是脚本中的默认范围,因此在脚本的顶层,您创建的所有变量都隐含在脚本范围内;因此,$script:Shell32 = ... 在脚本的顶级范围内实际上与 $Shell32 = ... 相同。您甚至可以从函数内部引用该脚本级变量,仅作为 $Shell32(尽管为了清楚起见,您可能希望使用 $script:Shell32)。您唯一 需要 $script: 范围限定符的情况是您创建了 local $Shell32 变量(例如,隐含地,简单地通过分配$Shell32shadows脚本级。

  • 下面的代码是一个重构,它创建了一个带有 Add-Type -TypeDefinition 的单个助手 class,其中集成了 P/Invoke 签名(不需要单独的 Add-Type -MemberDefinition呼唤)。

    • 创建的助手类型只有一个static方法,GetVerbName()。请注意,我已经从 P/Invoke 签名中删除了 public 访问修饰符以使它们成为私有的,因为它们现在只需要 class-internally.

    • 辅助方法在每次调用时加载和释放 "shell32.dll DLL,但我认为这不会成为性能方面的问题。

    • 您可以扩展此方法以将 所有 您的非 PowerShell 代码移动到此帮助程序 class(调用 Shell.Application COM目的)。如果您这样做并实现了一个 PinToTaskBar 静态方法,您可以简单地从脚本中的任何地方引用 [PxTools.TaskBarStartMenuHelper]::PinToTaskBar(),甚至不必担心脚本级变量。

  • 我用更清晰的 Enum 定义替换了 $verbs 哈希表,但请注意,这仅适用于 PSv5+。

Enum TaskBarStartMenuVerbs {  
  PinToStartMenu = 5384
  UnpinFromStartMenu = 5385 
  PinToTaskbar = 5386
  UnpinFromTaskbar = 5387
}

Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace PxTools {    
  public class TaskBarStartMenuHelper {
    
    [DllImport("user32.dll")] 
    static extern int LoadString(IntPtr h, uint id, System.Text.StringBuilder sb, int maxBuffer); 
    [DllImport("kernel32.dll")] 
    static extern IntPtr LoadLibrary(string s);
    [DllImport("kernel32.dll")] 
    static extern bool FreeLibrary(IntPtr h);
    
    public static string GetVerbName(uint verbId) {
      IntPtr h = LoadLibrary("shell32.dll");
      const int maxLen = 255;
      var sb = new StringBuilder(maxLen);
      LoadString(h, verbId, sb, maxLen);
      FreeLibrary(h);
      return sb.ToString();
    }         
  }    
}
'@ 

# This returns 'Pin to tas&kbar' on a US English system.
[PxTools.TaskBarStartMenuHelper]::GetVerbName([TaskBarStartMenuVerbs]::PinToTaskbar)

另请注意,只要类型定义未更改,在会话中重复调用 Add-Type 就可以了。随后的调用实际上是一个快速而安静的空操作。
换句话说:无需显式检查类型是否存在并有条件地定义它。如果已加载同名的 different 类型,则 Add-Type 将失败,但这是可取的,因为您要确保您使用的是该类型你要。


背景信息 -MemberDefinition

参考:Get-Help Add-Type.

  • 第一种方法 - AddType -MemberDefinition ... -Name ... -NameSpace 通常用于传递包含 public P/Invoke 签名 的 字符串 允许通过自定义 helper class 访问本机代码,例如 Windows API 函数 静态方法.

    • 当执行第一个命令时,类型[PxTools.Util]是用静态方法[PxTools.Util]::LoadString()[PxTools.Util]::LoadLibrary()创建的,调用底层WindowsAPI 函数。

    • 虽然 -MemberDefinition 通常 与 P/Invoke 签名一起使用,但不限于此,因为 -MemberDefinition只是 用于将指定字符串包装在 public class 中的语法糖(详见下文)。因此,您可以在 class 主体 中传递任何有效的 ,例如 属性 和方法定义,作为定义自定义 class 更简洁,无需包含显式 namespaceclass 块。
      但是请注意,总是创建一个 class,因此您不能使用这种方法来定义一个 struct,例如;此外,您不能直接使用 using 语句;相反,通过 -UsingNamespace 参数传递名称 spaces。

    • 不指定 -NameSpace 值会将类型放在名称 space Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes 中,这意味着您必须将其引用为 [Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.<yourTypeName>](请注意这与没有 namespace <name> { ... } 附件的 -TypeDefinition 命令有何不同 - 见下文)。

  • 第二种方法 - Add-Type -TypeDefinition ... - 用于 通过 [=] 定义自定义类型 (class) 167=]包含 C# 源代码的字符串(默认;也支持 VisualBasic 和 JScript)。
    自定义类型可以是 classdelegateenuminterfacestruct 定义,通常包含在 namespace 声明中。

    • 当执行第二个命令时,类型 [BasicTest] 被创建(没有名称 space 限定,因为源代码字符串中的类型定义没有包含在 namespace <name> { ... } 中), 静态方法 Add 和实例方法 Multiply.

在这两种情况下,成员都必须声明为 public 才能从 PowerShell 访问。

请注意,-MemberDefinition 语法本质上只是语法糖 ,因为它会自动提供 class 围绕 P/Invoke 签名的包装器在您想从 PowerShell 直接 调用它们而不是在用 Add-Type -TypeDefinition.

例子:

以下-MemberDefinition调用:

Add-Type -Namespace PxTools -Name Utility -MemberDefinition @'
    [DllImport("user32.dll")]
    public static extern int LoadString(IntPtr h, uint id, System.Text.StringBuilder sb, int maxBuffer);
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string s);
    [DllImport("kernel32.dll")] 
    public static extern bool FreeLibrary(IntPtr h);
'@  

是以下 -TypeDefinition 调用的语法糖:

Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;

namespace PxTools {
  public class Utility {
    [DllImport("user32.dll")]
    public static extern int LoadString(IntPtr h, uint id, System.Text.StringBuilder sb, int maxBuffer);
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string s);
    [DllImport("kernel32.dll")] 
    public static extern bool FreeLibrary(IntPtr h);
  }  
}
'@  

换句话说:Add-Type -MemberDefinition:

  • 自动包装源代码中定义的public静态方法在publicclass在指定名称space(-NameSpace)中加上指定名称(-Name),这样这些方法就可以被称为 class 上的静态方法,如 [<NameSpace>.<Name>]::<Method>(...)

    • 虽然这通常用于公开 P/Invoke 签名 - 调用非托管函数,特别是 Windows API 函数 - 但不限于此,您还可以使用通常使用包含常规(托管)C# 代码的 (public) 静态实用程序方法定义通用帮助程序 classes 的技术。请注意,您不能直接使用 using 语句;相反,通过 -UsingNamespace 参数传递名称 spaces。
  • 隐式预先添加必要的 using 语句 以支持编译 P/Invoke 签名。