有人可以解释这种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并不是一件大事吗?
答案 0 :(得分:0)
是的,以这种方式使用here-string是最好的方法。
在旁注中,使用:
将参数名称与其值分开,但不常见;通常,使用空格(例如-name Utility
而不是-name:Utility
)。
没有充分的理由在类型定义中使用[System.Text.StringBuilder]
使用here-string,常规字符串或字符串数组
除了在Windows API调用中使用之外,正如您所展示的那样,您在PowerShell中考虑使用[System.Text.StringBuilder]
的唯一原因是性能是否至关重要,您需要从动态创建的部分构建一个非常大的字符串
Gordon本人指出,在[System.Text.StringBuilder]
Windows API函数的sb
参数中使用LoadString()
是必要的,因为它是 out 参数接收字符串,而[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()
。请注意,我已从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
将失败,但这是合乎需要的,因为您要确保您使用的类型是想。
功能
第一种方法 - 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 ...
,class
,delegate
,enum
或interface
定义,通常包含在命名空间声明中。
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);
}
}
'@
:
Add-Type -MemberDefinition
语句。