NSIS System 插件
版权所有 © 2002 brainsucker (Nik Medved)
版权所有 © 2002-2023 NSIS 贡献者
目录
介绍
System 插件使开发人员能够从任何 DLL 调用任何导出的函数。 例如,您可以使用它调用 GetLogicalDriveStrings 以获取用户计算机上可用驱动器的列表。
System 插件还允许开发人员分配、释放和复制内存; 与 COM 对象交互并对 64 位整数执行数学运算。
强烈建议您具备编程知识,以便更好地理解 System 插件。
可用函数
内存相关函数
- Alloc SIZE
分配 SIZE 字节并返回一个内存地址到堆栈上。
使用范例
System::Alloc 64
Pop $0
DetailPrint "64字节分配到 $0"
System::Free $0
- StrAlloc SIZE
为 SIZE TCHARs 分配一个字符串缓冲区并返回堆栈上的内存地址。 如果您想编写一个适用于 ANSI 或者 Unicode NSIS 的 NSI 脚本,这将非常有用。
例子
System::StrAlloc 64 ; 用于 63 个字符加 \0 终止符的字符串缓冲区。
Pop $0
DetailPrint "64个字符的字符串缓冲区分配到 $0"
System::Free $0
- Copy [/SIZE] DESTINATION SOURCE
将 SIZE 个字节从 SOURCE 复制到 DESTINATION。 如果未指定 SIZE,则 SOURCE 的大小将使用 GlobalSize 查询。 这意味着如果您不使用 System::Alloc、System::Call 或 GlobalAlloc 分配 SOURCE,则必须指定 SIZE。 如果 DESTINATION 为零,它将被分配并将其地址压入堆栈。
例子
# 分配一个缓冲区并在其中放入“test string”字符串和一个 int 整数
System::Call "*(&t1024 'test string', i 5) p .s"
Pop $0
# 复制到自动创建的缓冲区
System::Copy 0 $0
Pop $1
# 在 $1 缓冲区中获取字符串和整数
System::Call "*$1(&t1024 .r2, i .r3)"
# 释放缓冲区
System::Free $1
# 输出结果
DetailPrint $2
DetailPrint $3
# 复制到我们自己的缓冲区
System::Alloc 1028
Pop $1
System::Copy $1 $0
# 在$1缓冲区中获取字符串和 int
System::Call "*$1(&t1024 .r2, i .r3)"
# 释放
System::Free $0
System::Free $1
#输出结果
DetailPrint $2
DetailPrint $3
- Free ADDRESS
释放 ADDRESS (内存地址).
例子
System::Alloc 64
Pop $0
DetailPrint "64个字节分配到了 $0"
System::Free $0
- Store "OPERATION [OPERATION [OPERATION ...]]"
执行堆栈操作。 操作可以是从 NSIS 堆栈中压入或弹出单个变量,也可以是从系统的私有堆栈中压入或弹出所有寄存器变量($0-$9 和 $R0-$R9)。 操作可以用任何字符分隔。
可用操作
- 要压入 $#,请使用 p#,其中 # 是0到9的数字。
- 要弹出 $#,请使用 r#,其中 # 是从 0 到 9 的数字。
- 要压入 $R#,请使用 P#,其中 # 是从 0 到 9 的数字。
- 要弹出 $R#,请使用 R#,其中 # 是从 0 到 9 的数字。
- 将 $0-$9 和 $R0-$R9 压入系统的私有堆栈,请使用 s 或 S。
- 要从系统的私有堆栈中弹出 $0-$9 和 $R0-$R9,请使用 l 或 L。
- 要将 NSIS 内部定义标志 (api.h 里的 exec_flags_t) 推送到堆栈,请使用 F。
- 要从堆栈中弹出 NSIS 内部定义标志(api.h 里的 exec_flags_t),请使用 f。
例子
StrCpy $0 "test"
System::Store "p0"
Pop $1
DetailPrint "$0 = $1"
StrCpy $2 "test"
System::Store "p2 R2"
DetailPrint "$2 = $R2"
StrCpy $3 "test"
System::Store "s"
StrCpy $3 "另一个测试"
System::Store "l"
DetailPrint $3
System::Store "r4" "测试"
DetailPrint $4
调用函数
- Call PROC [( PARAMS ) [RETURN [? OPTIONS]]]
- Get PROC [( PARAMS ) [RETURN [? OPTIONS]]]
Call 和 get 共享一个通用语法。 顾名思义,Call 调用 Get 获取。 它 Call 什么或 Get 什么取决于 PROC 的值。
PARAMS 是参数列表以及如何处理这些列表。 您可以在参数中传递数据,也可以从中获取数据。 参数列表以逗号分隔。 每个参数由三个值组合而成:type、source 和 destination。 Type 可以是整数,字符串等。 Source,也就是参数值的源,可以是NSIS寄存器变量($0, $1, $INSTDIR), NSIS 堆栈,具体值(5、“test”等)或什么都没有(null)。 Destination是调用返回后参数值的目标,可以是一个 NSIS 寄存器变量,NSIS 栈或者什么都不需要输出。 如果不需要,source 或 destination 之一也可以是点 (`.')。
RETURN 就像一个单一的参数定义,但 source 只在创建回调函数时使用。 通常 source 是一个点。
OPTIONS 是控制 System 插件行为方式的选项列表。 每个选项都可以通过在前面加上感叹号来关闭。 例如:?!e。
PARAMS、RETURN 和OPTIONS 可以在一个 Get/Call 行中重复多次。 这样重复的时候可以省很多事,只改自己想改的就行。 Type、source 和/或 destination 可以省略每个参数,甚至是返回值。 可以添加或删除选项。 这允许您定义函数原型并节省一些输入。 最后两个例子展示了这一点。
PROC 也可以重复,但它必须以井号 (`#') 为前缀,井号前面有双冒号 (shell32::#18 ) 的情况下,它会被解释为函数序数。
PROC 可能的值和含义
可用参数类型
类型 | 含义 |
v |
void (无类型,常用于返回) |
|
p |
指针(以及其他指针大小的类型,比如通用句柄 handle 和窗口句柄 HWND)) |
b |
int8, 1字节 |
h |
int16, short 2个字节 |
i |
int32(当用作指针时包括char、byte、short等) |
l |
int64, 长整数 |
m |
ANSI 文本,字符串(仅供参考:'m' 表示多字节字符串或 'w' 翻过来。) |
t |
文本,字符串(指向第一个字符的指针)。 和TCHAR*一样,是 Unicode NSIS 中的 Unicode 字符串。 |
w |
WCHAR 文本, Unicode 字符串 |
g |
GUID |
k |
回调 |
@ |
直接寄存器内存访问(缓冲区限制为 (NSIS_MAX_STRLEN - 24) * NSIS_CHAR_SIZE 字节) |
&vN |
N 字节填充(仅限结构) |
&iN |
N 字节的整数(仅限结构) |
&l |
结构的大小 |
&tN |
N TCHAR 文本字符数组(仅限结构) |
&mN |
N CHAR ANSI 字符数组(仅限结构) |
&wN |
N 个 WCHAR Unicode 字符数组(仅限结构) |
&g16 |
16 字节的 GUID(仅限结构) |
此外,每种类型(b、h、k 和 @ 除外)都可以使用星号作为前缀来表示 一个指针。 当使用一个星号时,System 插件仍然认为是参数的值,而不是指针的地址。 要传递直接地址,请使用不带星号的“p”。 一个使用示例 。 Alloc 返回地址,因此它的返回值应该与 `p' 一起使用,不带星号。
可以使用的源和目标
类型 | 含义 |
. |
忽略 |
|
number |
具体的十六进制、十进制或八进制整数值。可以使用符号(“|”)对几个整数进行“或”运算 |
'string' "string" `string` |
具体字符串值 |
r0 到 r9 |
分别为$0到$9 |
r10 到 r19 R0 到 R9 |
分别为$R0到$R9 |
c |
$CMDLINE |
d |
$INSTDIR |
o |
$OUTDIR |
e |
$EXEDIR |
a |
$LANGUAGE |
s |
NSIS 堆栈 |
n |
源为空,目标不需要输出 |
使用@类型时,源是必需的,并且必须是寄存器变量。当调用返回时,源寄存器已包含字符串形式的内存地址,因此使用目标通常没有必要。
回调
回调函数只是一个传递给函数并由函数自身回调的函数。 它们经常用于逐项传递可能很大的数据集。 例如,EnumChildWindows 使用 回调函数。 由于 NSIS 函数不是很常规的函数,System 插件提供了自己的机制来专门支持回调函数。 它允许您创建回调函数并在每次调用回调函数时通知您。
回调函数的创建是使用 Get 和回调创建语法完成的。 由于您不能自己调用回调,因此应使用点省略参数的源。调用回调时,参数的目标将填充传递给回调的值。回调将返回的值由返回“参数”的源设置。返回“参数”的目标应始终设置为System通知您调用回调的位置。
System::Get "(i .r0, i .r1) iss"
要向函数传递回调,请使用k类型。
System::Get "(i .r0, i .r1) isR0"
Pop $0
System::Call "dll::UseCallback(k r0)"
每次调用回调时,字符串 callback#(#是回调的编号),将被放置在返回“参数”的目标中。 创建的第一个回调的编号是 1,第二个是 2,第三个是 3,依此类推。 由于 System 是单线程的,回调只能在调用另一个函数时调用。 例如,EnumChildWindows 的回调只能在 EnumChildWindows 被调用时调用。 因此,您应该在每次可能调用回调的函数调用后检查 callback#。
System::Get "(i .r0, i .r1) isR0"
Pop $0
System::Call "dll::UseCallback(k r0)"
StrCmp $R0 "callback1" 0 +2
DetailPrint "UseCallback 将 ($0, $1) 传递给回调"
处理回调调用后,您应该使用 Call,将 Get 返回的值传递给回调 . 这告诉 System 从回调中返回。 在调用函数之前必须清除返回“参数”的目标,以避免错误检测回调调用。 如果您在创建回调时为返回“参数”指定了源,则应使用适当的返回值填充该源。 回调不会自动释放,不要忘记在使用完后释放它。
System::Get "(i .r0, i .r1) isR0"
Pop $0
System::Call "dll::UseCallback(k r0)"
loop:
StrCmp $R0 "callback1" 0 done
DetailPrint "UseCallback 将 ($0, $1) 传递给回调"
Push 1 # 回调的返回值
StrCpy $R0 "" # 清除 $R0 以防没有更多的回调调用
System::Call $0 # 通知 System 从回调中返回
Goto loop
done:
System::Free $0
完整的示例。
注意:
- 若要查找COM接口中的成员索引,需要在 Visual C/C++ 或 Platform SDK 附带的头文件中搜索此COM接口的定义。索引从零开始。
- 如果找不到函数或使用了 t 参数类型,则将在其名称后附加“a”或“W”,然后再次查找该函数。这样做是因为许多 Windows API 函数有两个版本,一个用于ANSI字符串,另一个用于 Unicode 字符串。函数的 ANSI 版本用“A”标记,Unicode 版本用“W”标记。例如:lstrcpyA 和 lstrcpyW。
- 可以在没有路径的情况下加载 system32 目录中的库。所有其他库都应使用带引号的完整路径加载。
可用选项
选项 | 意义 |
c |
cdecl 调用约定(调用者恢复的堆栈)。 默认情况下,在 x86 上使用 stdcall 调用约定(被调用者恢复的堆栈)。 |
|
r |
始终返回(对于GET意味着您应该弹出结果和过程,对于CALL意味着您应该至少弹出结果)。默认情况下,只为错误返回结果(对于GET,您将弹出错误结果或正确的过程,对于CALL,您将在定义的返回位置获得返回值或结果)。 |
n |
无重新定义。 无论何时使用此过程,它都不会被 GET 或 CALL 重新定义。 此选项永远不会继承给子项。 |
s |
使用通用堆栈。 每当定义第一个回调时,系统就会开始使用临时堆栈进行函数调用。 |
e |
过程结束后调用 GetLastError() 并将结果压入堆栈。 |
u |
调用后使用 FreeLibrary 卸载DLL。 |
2 |
实验性v2语法 |
实验性 v2 语法
例子
System::Call 'user32::MessageBox(p $HWNDPARENT, t "NSIS System Plug-in", t "Test", i 0)'
System::Call '"$SysDir\MyLibrary.dll"::MyFunction(i 42)'
System::Call "kernel32::GetModuleHandle(t 'user32.dll') p .s"
System::Call "kernel32::GetProcAddress(p s, m 'MessageBoxA') p .r0"
System::Call "::$0(p $HWNDPARENT, m 'GetProcAddress test', m 'NSIS System Plug-in', i 0)"
System::Get "user32::MessageBox(p $HWNDPARENT, t 'This is a default text', t 'Default', i 0)"
Pop $0
System::Call "$0"
System::Get "user32::MessageBox(p $HWNDPARENT, t 'This is a default text', \
t 'Default', i 0x1|0x10)"
Pop $0
System::Call "$0(, 'This is a System::Get test', 'NSIS System Plug-in',)"
System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2"
DetailPrint "User name - $0"
DetailPrint "String length - $1"
DetailPrint "Return value - $2"
System::Alloc 4
Pop $0
System::Call "*$0(i 5)" ; Write
System::Call "*$0(i .r1)" ; Read
System::Free $0
DetailPrint $1
System::Call "*(i 5) p .r0"
System::Call "*$0(i .r1)"
System::Free $0
DetailPrint $1
System::Call '*0(p, &l.r2, &t2)' ; &l. is not part of the struct
DetailPrint "Struct size=$2"
System::Call '*(&l4,i,i,i,i,&t128)p.r1' ; Fills dwOSVersionInfoSize with the struct size as a int32
${If} $1 Z<> 0
System::Call 'kernel32::GetVersionEx(pr1)i.r0'
System::Call '*$1(i,i.R1,i.R2,i.R3)'
System::Free $1
${IfThen} $0 <> 0 ${|} DetailPrint "v$R1.$R2.$R3" ${|}
${EndIf}
System::Call "user32::GetClientRect(p $hwndparent, @ r0)"
System::Call "*$0(i,i,i.r1,i.r2)"
DetailPrint ClientRect=$1x$2
# defines
!define CLSCTX_INPROC_SERVER 1
!define CLSID_ActiveDesktop {75048700-EF1F-11D0-9888-006097DEACF9}
!define IID_IActiveDesktop {F490EB00-1240-11D1-9888-006097DEACF9}
# create IActiveDesktop interface
System::Call "ole32::CoCreateInstance(
\ g '${CLSID_ActiveDesktop}', p 0,
\ i ${CLSCTX_INPROC_SERVER},
\ g '${IID_IActiveDesktop}', *p .r0)
i.r1" StrCmp $1 0 0
end # callIActiveDesktop->GetWallpaper
System::Call "$0->4(w .r2, i ${NSIS_MAX_STRLEN}, i
0)" # callIActiveDesktop->Release
System::Call "$0->2()"
# print result
DetailPrint $2
end:
InitPluginsDir
File "/oname=$PLUGINSDIR\MyDLL.dll" MyDLL.dll
System::Call 'KERNEL32::AddDllDirectory(w "$PLUGINSDIR")'
System::Call 'KERNEL32::LoadLibrary(t "$PLUGINSDIR\MyDLL.dll")p.r1'
System::Call 'MyDLL::MyFunc(i 5) ? u'
System::Call 'KERNEL32::FreeLibrary(pr1)'
Delete $PLUGINSDIR\MyDLL.dll
System::Get "(p.r1, p) iss"
Pop $R0
System::Call "user32::EnumChildWindows(p $HWNDPARENT, k R0, p) i.s"
loop:
Pop $0
StrCmp $0 "callback1" 0 done
System::Call "user32::GetWindowText(pr1,t.r2,i${NSIS_MAX_STRLEN})"
System::Call "user32::GetClassName(pr1,t.r3,i${NSIS_MAX_STRLEN})"
IntFmt $1 "0x%X" $1
DetailPrint "$1 - [$3] $2"
Push 1 # callback's return value
System::Call "$R0"
Goto loop
done:
System::Free $R0
DetailPrint "EnumChildWindows returned $0"
System::Get '(m.r1)ir2r0 ?2' ; v2 syntax
Pop $9
System::Call 'kernel32::EnumSystemLocalesA(k r9, i 0)'
loop:
StrCmp $0 "callback$9" 0 done
DetailPrint "Locale: $1"
StrCpy $2 1 ; EnumLocalesProc return value
System::Call $9 ; return from EnumLocalesProc
Goto loop
done:
System::Free $9
System::Call '*(&t50 "!")p.r2' ; DecimalSep
System::Call '*(&t50 "`")p.r3' ; ThousandSep
System::Call '*(i 2, i 0, i 3, P r2, P r3, i 1)p.r1 ?2'
System::Call 'kernel32::GetNumberFormat(i 0, i 0, t "1337.666" r4, p r1, t.r5, i ${NSIS_MAX_STRLEN})'
DetailPrint "Custom formated $4: $5"
System::Free $3
System::Free $2
System::Free $1
!define MB "user32::MessageBox(p$HWNDPARENT,t,t'NSIS System Plug-in',i0)"
System::Call "${MB}(,'my message',,)"
System::Call "${MB}(,'another message',,) i.r0"
MessageBox MB_OK "last call returned $0"
System::Call "user32::SendMessage(p $HWNDPARENT, t 'test', t 'test', p 0) p.s ? \
e (,t'test replacement',,) i.r0 ? !e #user32::MessageBox"
DetailPrint $0
ClearErrors
Pop $0
IfErrors good
MessageBox MB_OK "this message box will never be reached"
good:
64-bit 函数
- Int64Op ARG1 OP [ARG2]
对 ARG1 和可选的 ARG2 执行 OP 运算,并将结果返回堆栈。 ARG1 和 ARG2 都是 64 位整数。 这意味着它们的范围可以在 -2^63 和 2^63 - 1 之间。
可执行操作
- 加法 -- +
- 减法 -- -
- 乘法 -- *
- 除法 -- /
- 取模 -- %
- 左移 -- <<
- 算术右移 -- >>
- 逻辑右移 -- >>>
- 位或 -- |
- 位与 -- &
- 位异或 -- ^
- 按位非(一个参数) -- ~
- 逻辑非(一个参数) -- !
- 逻辑或 -- ||
- 逻辑与 -- &&
- 小于 -- <
- 等于 -- =
- 大于 -- >
应用范例
System::Int64Op 5 + 5
Pop $0
DetailPrint "5 + 5 = $0" # 10
System::Int64Op 526355 * 1565487
Pop $0
DetailPrint "526355 * 1565487 = $0" # 824001909885
System::Int64Op 5498449498849818 / 3
Pop $0
DetailPrint "5498449498849818 / 3 = $0" # 1832816499616606
System::Int64Op 0x89498A198E4566C % 157
Pop $0
DetailPrint "0x89498A198E4566C % 157 = $0" # 118
System::Int64Op 1 << 62
Pop $0
DetailPrint "1 << 62 = $0" # 4611686018427387904
System::Int64Op 0x4000000000000000 >> 62
Pop $0
DetailPrint "0x4000000000000000 >> 62 = $0" # 1
System::Int64Op 0x8000000000000000 >> 1
Pop $0
DetailPrint "0x8000000000000000 >> 1 = $0" # -4611686018427387904 (0xC000000000000000)
System::Int64Op 0x8000000000000000 >>> 1
Pop $0
DetailPrint "0x8000000000000000 >>> 1 = $0" # 4611686018427387904 (0x4000000000000000)
System::Int64Op 0x12345678 & 0xF0F0F0F0
Pop $0
# IntFmt is 32-bit, this is just for the example
IntFmt $0 "0x%X" $0
DetailPrint "0x12345678 & 0xF0F0F0F0 = $0" # 0x10305070
System::Int64Op 1 ^ 0
Pop $0
DetailPrint "1 ^ 0 = $0" # 1
System::Int64Op 1 || 0
Pop $0
DetailPrint "1 || 0 = $0" # 1
System::Int64Op 1 && 0
Pop $0
DetailPrint "1 && 0 = $0" # 0
System::Int64Op 9302157012375 < 570197509190760
Pop $0
DetailPrint "9302157012375 < 570197509190760 = $0" # 1
System::Int64Op 5168 > 89873
Pop $0
DetailPrint "5168 > 89873 = $0" # 0
System::Int64Op 189189 = 189189
Pop $0
DetailPrint "189189 = 189189 = $0" # 1
System::Int64Op 156545668489 ~
Pop $0
DetailPrint "156545668489 ~ = $0" # -156545668490
System::Int64Op 1 !
Pop $0
DetailPrint "1 ! = $0" # 0
FAQ
- Q: 如何将结构传递给函数?
A: 首先,您必须分配结构。 这可以通过使用具有特殊结构分配语法的 Alloc 或 Call来完成。 接下来,如果你需要在结构中传递数据,那么你必须用数据填充它。 然后使用指向该结构的指针调用该函数。 最后,如果你想从被调用函数写入的结构中读取数据,则必须在结构处理语法中使用 Call 。 完成所有操作后,重要的是要记住释放结构。
分配
要使用 Alloc 分配结构,您必须知道结构的大小(以字节为单位)。 因此,通常使用 Call 会更容易。 在下面的例子中,很容易看出所需的大小是 16 个字节,但在其他情况下可能就不是那么简单了。 在这两种情况下,结构地址都位于堆栈的顶部,应该使用 Pop 弹出到变量来检索。
System::Alloc 16
System::Call "*(i, i, i, t)p.s"
设置数据
可以使用Call 来设置数据。 它可以在分配阶段完成,也可以在另一个阶段使用结构处理语法完成。
System::Call "*(i 5, i 2, i 513, t 'test')p.s"
# 假设结构的内存地址保存在 $0
System::Call "*$0(i 5, i 2, i 513, t 'test')"
传递给函数
由于所有分配方法都返回一个地址,因此传递的数据类型应该是一个整数,一个结构体在内存中的地址。
#假设结构的内存地址保存在 $0
System::Call "dll::func(p r0)"
读取数据
可以使用与设置结构相同的语法从结构中读取数据。唯一的区别是将设置参数的目标部分,而使用点省略源部分。
# 假设结构的内存地址保持在$0中
System::Call "*$0(i .r0, i .r1, i .r2, t .r3)"
DetailPrint "first int = $0"
DetailPrint "second int = $1"
DetailPrint "third int = $2"
DetailPrint "string = $3"
释放内存
使用Free 释放内存.
# 假设结构体的内存地址保存在 $0 中
System::Free $0
完整示例
# 分配
System::Call "*(i,i,p,p,p,p,p,p)p.r1"
# call
System::Call "Kernel32::GlobalMemoryStatus(p r1)"
# get
System::Call "*$1(i.r2, i.r3, p.r4, p.r5, p.r6, p.r7, p.r8, p.r9)"
# 释放
System::Free $1
# 窗口信息输出
DetailPrint "结构尺寸: $2 bytes"
DetailPrint "内存负载 : $3%"
DetailPrint "总物理内存: $4 bytes"
DetailPrint "释放物理内存: $5 bytes"
DetailPrint "总页面文件: $6 bytes"
DetailPrint "释放页面文件: $7 bytes"
DetailPrint "虚拟内存总计: $8 bytes"
DetailPrint "释放虚拟内存: $9 bytes"
水晶石 2022.12.31
|