.net - 添加类型变化 : -MemberType vs. -TypeDefinition 参数

标签 .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);
    }
}
"@

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

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

我已经根据从 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位需要像这样处理,或者它不能被卷入类型吗?而且,原来的全局范围(我改为脚本范围)真的有必要吗?在我看来,除非我这样做数百次,否则多次调用 LoadLibrary 不会有什么大不了的?

最佳答案

  • 回复 1:

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

    附带说明,使用 :将参数名称与其值分开是可行的,但不寻常;通常,使用空格(例如, -name Utility 而不是 -name:Utility )。
  • 回复 2:

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

    戈登本人指出,使用 [System.Text.StringBuilder]sb LoadString() 的参数Windows API 函数是必须的,因为它是一个接收字符串的输出参数,而 [string]类型是不可变的。
  • 回复 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() .请注意,我已经删除了 public访问修饰符从 P/Invoke 签名使它们私有(private),因为它们现在只在类内部需要。
  • 辅助方法加载和释放 "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在 session 中重复,只要类型定义没有改变。后续调用实际上是一种快速且无声的空操作。
    换句话说:不需要显式检查类型的存在并有条件地定义它。如果已经加载了同名的不同类型,则 Add-Type会失败,但这是可取的,因为您想确保使用的是您想要的类型。


    背景资料

    引用: Get-Help Add-Type .
  • 第一种方法 - AddType -MemberDefinition ... -Name ... -NameSpace通常用于传递 包含公共(public)的字符串 P/Invoke签名那个允许访问 native 代码,例如 Windows API 函数 通过自定义 辅助类将它们暴露为 静态方法 .
  • 执行第一条命令时,输入 [PxTools.Util]使用静态方法创建 [PxTools.Util]::LoadString()[PxTools.Util]::LoadLibrary()调用底层的 Windows API 函数。
  • 虽然 -MemberDefinition通常与 P/Invoke 签名一起使用,但不限于此,因为 -MemberDefinition只是将指定字符串包装在公共(public)类中的语法糖(有关详细信息,请参见下文)。因此,您可以传递任何在 class 中有效的内容。主体,例如属性和方法定义,作为更简洁地定义自定义类的一种方式,而不必包含显式 namespaceclass块。
    但是请注意,它总是一个 class已创建,因此您不能使用此方法定义 struct , 例如;此外,您不能使用 using直接陈述;相反,通过 -UsingNamespace 传递命名空间范围。
  • 未指定 -NameSpace value 将类型放入命名空间 Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes ,这意味着您必须将其称为 [Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.<yourTypeName>] (注意这与没有 -TypeDefinition shell 的 namespace <name> { ... } 命令有何不同 - 见下文)。
  • 第二种方法 - Add-Type -TypeDefinition ... - 用于定义自定义类型(类)通过 包含 C# 源代码的字符串 (默认情况下;也支持 VisualBasic 和 JScript)。
    自定义类型可以是 class , delegate , enum , interface , 或 struct定义,通常包含在命名空间声明中。
  • 执行第二条命令时,输入 [BasicTest]创建(没有命名空间限定,因为源代码字符串中的类型定义未包含在 namespace <name> { ... } 中),使用静态方法 Add和实例方法 Multiply .

  • 在这两种情况下,成员必须声明为 public以便从 PowerShell 访问。

    请注意 -MemberDefinition语法本质上只是语法糖 因为它会自动为 提供一个围绕 P/Invoke 签名的类包装器。在您想直接调用它们的情况下促进对 native DLL 调用的访问 来自 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)静态方法包装在指定 namespace ( -Name ) 中具有指定名称 ( -NameSpace ) 的公共(public)类中,以便这些方法可以作为该类的静态方法调用,如 [<NameSpace>.<Name>]::<Method>(...)
  • 虽然这通常用于公开 P/Invoke 签名 - 调用非托管函数,特别是 Windows API 函数 - 但不限于此,您还可以使用该技术通常使用(公共(public))静态实用程序定义通用帮助程序类包含常规(托管)C# 代码的方法。请注意,您不能使用 using直接陈述;相反,通过 -UsingNamespace 传递命名空间范围。
  • 隐含地前置必要的 using声明 需要支持编译 P/Invoke 签名。
  • 关于.net - 添加类型变化 : -MemberType vs. -TypeDefinition 参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40296537/

    相关文章:

    .net - Azure 文本到语音将 SpeakTextAsync 转换为有效的 NAudio 波流

    c# - C# 中的密集图形应用程序(使用 .NET/Mono)

    c# - 使用 IComparer 按另一个列表对带有自定义对象的列表进行排序

    azure - New-AzSqlDatabaseImport 无法将参数绑定(bind)到参数 'OperationStatusLink',因为它为 null

    Haskell 多重上下文 - 柯里化(Currying)?

    jquery 改变输入类型

    java - 如何将网站中的图表导出到 Excel?

    windows - 如何将文字双引号从 PowerShell 传递到 native 命令?

    .net - 字符串数组和 ConvertTo-HTML

    haskell - Haskell 中的函数依赖