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

时间:2016-10-28 01:19:45

标签: .net powershell types add-type

有人可以解释这种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);
    }
}
"@

我经常看到后者的例子,但前者我在一些示例代码中只见过Pin to Taskbar。这些只是两种不同的皮肤猫的方法,或者在某些用例中是前者需要的吗? 并且,如果两者都是有效的,那么将后一种方法与前者的代码一起使用会是什么样的呢?

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

我已根据我从this帖子...

所学到的内容实现了代码
$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? 编辑:这似乎有效,所以我将修改问题,这是最好/最优雅的解决方案,还是可以改进?

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 个答案:

答案 0 :(得分:0)

  • Re 1:

是的,以这种方式使用here-string是最好的方法。

在旁注中,使用:将参数名称与其值分开,但不常见;通常,使用空格(例如-name Utility而不是-name:Utility)。

  • Re 2:

没有充分的理由在类型定义中使用[System.Text.StringBuilder] 使用here-string,常规字符串或字符串数​​组 除了在Windows API调用中使用之外,正如您所展示的那样,您在PowerShell中考虑使用[System.Text.StringBuilder]的唯一原因是性能是否至关重要,您需要从动态创建的部分构建一个非常大的字符串

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

  • Re 3:

可以将两种方法结合起来 - 一方面P / Invoke签名[DllImport(...-MemberType,另一方面是自定义类型定义class BasicTest ... )另一方使用-TypeDefinition(有关这两种方法的背景信息,请参阅本文的底部)。

旁注重新script范围:script是脚本中的默认范围,因此在脚本的顶层,您创建的所有变量都是隐式脚本范围的;因此,$script:Shell32 = ...实际上与脚本顶级范围中的$Shell32 = ...相同。您甚至可以将函数内部的脚本级变量引用为$Shell32(尽管为了清晰起见,您可能希望使用$script:Shell32)。您需要 $script:范围限定符的唯一时间是,如果您已创建本地 $Shell32变量(例如,隐式地,仅仅通过分配$Shell32阴影脚本级别

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

    • 创建的帮助程序类型只有一个 静态方法GetVerbName()。请注意,我已从P / Invoke签名中删除了public访问修饰符,以使其成为私有,因为他们现在只需要 class-internal

    • 辅助方法在每次调用时加载并释放"shell32.dll DLL,但我不希望这是性能问题。

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

  • 我已使用更清晰的$verbs定义替换了Enum哈希表,但请注意,这仅适用于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就可以了。随后的调用实际上是一个快速而安静的无操作 换句话说:无需明确检查类型的存在并定义有条件地。如果已经加载了同名的不同类型,则Add-Type将失败,但这是合乎需要的,因为您要确保您使用的类型是想。 功能

背景资料

参考:Get-Help Add-Type

  • 第一种方法 - AddType -MemberDefinition ... -Name ... -NameSpace通常用于传递包含公共P/Invoke签名的字符串允许访问本机代码,例如Windows API功能通过自定义帮助类将其公开为静态方法

    • 执行第一个命令时,使用调用基础Windows API函数的静态方法[PxTools.Util][PxTools.Util]::LoadString()创建类型[PxTools.Util]::LoadLibrary()

    • 虽然-MemberDefinition 通常与P / Invoke签名一起使用,但并不仅限于此,因为-MemberDefinition只是用于包装的语法糖公共类中指定的字符串(有关详细信息,请参见下文)。因此,您可以在class正文中传递任何有效的内容,例如属性和方法定义,作为更简洁地定义自定义类的方法,而不必包含显式{{ 1}}和namespace块。
      但请注意,总是class已创建,因此您无法使用此方法定义class,更重要的是,您无法使用struct语句,使这种方法变得不切实际。

    • 未指定using值会将该类型置于命名空间-NameSpace中,这意味着您必须将其称为Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes(请注意它与没有[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.<yourTypeName>]附件的-TypeDefinition命令 - 见下文)。

  • 第二种方法 - namespace <name> { ... } - 用于通过包含C#源代码字符串定义自定义类型(类)(默认情况下;还支持VisualBasic和JScript。) 自定义类型可以是Add-Type -TypeDefinition ...classdelegateenuminterface定义,通常包含在命名空间声明中。

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

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

请注意, public语法基本上只是语法糖,因为它会自动提供围绕P / Invoke签名的类包装器,以便便于在案例中访问本机DLL调用您希望从PowerShell直接调用 的地方,而不是在使用-MemberDefinition定义的自定义类型内部使用它们。

示例

以下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); } } '@

  • 自动将P / Invoke签名包装在指定名称空间中具有指定名称的公共类中,使得该类的P / Invoke签名静态方法声明的DLL函数
  • 并为P / Invoke签名添加必要的Add-Type -MemberDefinition语句。