有人可以解释这种方法与 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 不会有什么大不了的?
最佳答案
是的,以这种方式使用 here-string 是最好的方法。
附带说明,使用
:
将参数名称与其值分开是可行的,但不寻常;通常,使用空格(例如, -name Utility
而不是 -name:Utility
)。没有充分的理由使用
[System.Text.StringBuilder]
在类型定义中。使用此处字符串、常规字符串或字符串数组。
除了在 Windows API 调用中使用之外,正如您所展示的,这是您考虑使用
[System.Text.StringBuilder]
的唯一原因。在 PowerShell 中,如果性能是最重要的,并且您需要从动态创建的片段中构建一个非常大的字符串。戈登本人指出,使用
[System.Text.StringBuilder]
在 sb
LoadString()
的参数Windows API 函数是必须的,因为它是一个接收字符串的输出参数,而 [string]
类型是不可变的。可以将这两种方法结合起来 - 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 每次调用,但我不希望这是一个明智的性能问题。 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
中有效的内容。主体,例如属性和方法定义,作为更简洁地定义自定义类的一种方式,而不必包含显式 namespace
和 class
块。但是请注意,它总是一个
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
:-Name
) 中具有指定名称 ( -NameSpace
) 的公共(public)类中,以便这些方法可以作为该类的静态方法调用,如 [<NameSpace>.<Name>]::<Method>(...)
using
直接陈述;相反,通过 -UsingNamespace
传递命名空间范围。 using
声明 需要支持编译 P/Invoke 签名。 关于.net - 添加类型变化 : -MemberType vs. -TypeDefinition 参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40296537/