原文:https://nsis.sourceforge.io/Docs/System/System.html
抄抄写写的翻译
NSIS 系统插件
目录
• 介绍
• 可用功能
o 内存相关功能
o 调用函数
o 64 位函数
• 常见问题
介绍
系统插件使开发人员能够调用任何 DLL 的任何导出函数。例如,您可以使用系统插件调用GetLogicalDriveString来获取用户计算机上的可用驱动器的列表。
系统插件还允许开发人员分配、释放和拷贝内存;与 COM 对象交互,并在 64 位整数上执行数学运算。
强烈建议使用编程知识,以便更好地了解系统插件。
可用功能
内存相关功能
•Alloc SIZE
分配SIZE字节并在堆栈上返回内存地址。
使用示例
System::Alloc 64
Pop $0
DetailPrint "64 bytes allocated at $0"
System::Free $0
•StrAlloc SIZE
为 SIZE TCHAR 分配一个字符串缓冲区,并在堆栈上返回内存地址。如果您想要编写适用于 ANSI 和 Unicode NSIS 的 NSI 脚本,这非常有用。
使用示例
System::StrAlloc 64 ; String buffer for 63 characters and \0 termination.
Pop $0
DetailPrint "A string buffer for 64 characters allocated at $0"
System::Free $0
•Copy [/SIZE] DESTINATION SOURCE
将大小字节从SOURCE拷贝到DESTINATION。如果未指定SIZE,则SOURCE的大小将使用 GlobalSize 查询。这意味着,如果您不使用System::Alloc、System::Alloc 或者 GlobalAlloc 分配内存,则必须指定SIZE。 如果DESTINATION 为NULL,它将被分配,返回内存地址在堆栈上。
示例
# allocate a buffer and put 'test string' and an int in it
System::Call "*(&t1024 'test string', i 5) p .s"
Pop $0
# copy to an automatically created buffer
System::Copy 0 $0
Pop $1
# get string and int in $1 buffer
System::Call "*$1(&t1024 .r2, i .r3)"
# free buffer
System::Free $1
# print result
DetailPrint $2
DetailPrint $3
# copy to our own buffer
System::Alloc 1028
Pop $1
System::Copy $1 $0
# get string and int in $1 buffer
System::Call "*$1(&t1024 .r2, i .r3)"
# free
System::Free $0
System::Free $1
# print result
DetailPrint $2
DetailPrint $3
释放内存
示例
System::Alloc 64
Pop $0
DetailPrint "64 bytes allocated at $0"
System::Free $0
•Store "OPERATION [OPERATION [OPERATION ...]]"
堆栈操作。操作可以从 NSIS 堆栈Push或 Pop单个寄存器,或从 System 的专用堆栈中Push或Pop所有寄存器($0-$9和 $R0-$R9)。操作可以由任何字符分隔。
Available Operations
o 执行push s#,使用p#,#是0~9 的一个数字
o 执行pop s#,使用r#,#是0~9 的一个数字
o 执行push $R#,使用P#.,#是0~9 的一个数字
o 执行 pop $R#,使用 R#,#是0~9 的一个数字
o 当push $0-$9 和 $R0-$R9系统私有堆栈时, 使用 s 或S.
o 当pop $0-$9 和 $R0-$R9系统私有堆栈时, 使用l 或L.
示例:
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 "another test"
System::Store "l"
DetailPrint $3
System::Store "r4" "test"
DetailPrint $4
Calling Functions
• Call PROC [( PARAMS ) [RETURN [? OPTIONS]]]
• Get PROC [( PARAMS ) [RETURN [? OPTIONS]]]
Call和Get 两者语法相同。正如名称所描述,Call 就是调用Gets 获取。Call或Get 什么?这取决于PROC的值。
PARAMS是参数的列表,以及它们与参数的处理方法。您可以在参数中传递数据和获取数据。参数列表用逗号分隔。
每个参数由三个值组合:类型、源和目标。
类型可以是整数、字符串等,
源:就是参数值的源,可以是一个 NSIS 寄存器($0,$1,$INSTDIR)、NSIS堆栈、具体值 (5、"test"等)或无(null)。
目标:就是调用返回后参数值的目标,可以是 NSIS 变量、NSIS 堆栈或空。如果不需要源或目标之一,也可以用一个点 ('.')表示。
RETURN就像单个参数定义,但源仅在创建回调函数时使用。通常源是一个点。
OPTIONS是控制系统插件行为方式的选项列表。每个选项都可以通过使用感叹号前缀来关闭。例如:? !e.
PARAMS,RETURN和OPTIONS 可以在一个Get/Call调用中重复多次。重复时,可以省略很多,只使用您希望更改的。可以忽略每个参数的类型、源和/或目标,甚至返回值。可以添加或删除选项。这允许您定义函数原型并保存一些类型。最后两个示例显示了这一点。
PROC也可以重复,但必须用('#')前缀,除非(‘#’)符号前面有一个双冒号(::),在这种情况下,它被解释为函数序号。如: shell32::#18
可能的PROC 值和意义
值 | 意义 | 例子 |
DLL::FUNC | DLL输出的函数 | user32::MessageBox |
::ADDR | 地址为ADDR的函数 | 看下面 |
*ADDR | 地址为ADDR的结构 | 同上 |
* | 新结构 | 同上 |
IPTR->IDX | 成员索引 IDX 从界面由IPTR指向 | 同上 |
<nothing> | 新的回调函数 | 同上 |
PROC | 由Get 返回的PROC | 同上 |
有效的参数类型
类型 | 意义 |
v | void (常用于返回) |
p | 指针 (和其他已定义大小类型的指针,比如:handles 、HWNDs) |
b | Int8 ,byte 类型 |
h | Int16 ,short 类型 |
i | Int32(包括 char、byte、short、句柄、指针等等) |
I | Int64,长整数 |
m | ANSI text,string |
t | 文本,字符串(LPCSTR,指向第一个字符的指针),如TCHAR* ,在UNICODE NSIS 是一个UNICODE 字符串 |
w | WCHAR 文本,UNICODE字符串 |
g | GUID |
k | 回调 |
@ | 直接寄存器内存访问(Buffer 限制于字节大小) (NSIS_MAX_STRLEN - 24) * NSIS_CHAR_SIZE |
&iN | N字节整数 (仅结构) |
&l | 结构大小 (仅结构) |
&tN | N字节文本(仅结构) |
&mN | N字节Ansi 文本 (仅结构) |
&wN | N字节Unicode文本(仅结构) |
&g16 | 16 字节GUID (仅结构) |
此外,每种类型(b、h、k和#除外)都可以用星号前缀表示指针。使用星号时,System 插件仍需要参数的值,而不是指针地址。要传递直接地址,请使用没有星号的"p"。因此,Alloc返回地址及其返回值应与"p"一起使用,而不带星号。
使用@类型的源时,要求必须是寄存器。当调用返回时,源寄存器已经包含字符串形式的内存地址,因此通常不需要使用目标。
有效的源和目标
类型 | 意义 |
. (点) | 忽略 |
number | 16进制、10进制、8进制整数值。许多证书可以使用 | 来执行或操作 |
'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 stack |
n | 空的源,目标不需要输出 |
示例:
调用win32 API: int AddFontResource(LPCTSTR lpszFilename);
System::Call "GDI32::AddFontResource(t'$FONTS\${fontFilename}') i.r0"
# 返回值保存在$0
或者
System::Call "GDI32::AddFontResource(t'$FONTS\${fontFilename}') i.s"
Pop $R9
# 将返回值保存在NSIS堆栈中,使用Pop彈出到指定变量
调用win32 API:BOOL WINAPI GetWindowRect(HWND hWnd, LPRECT lpRect);
# 使用*来声明一个新的结构,和RECT一样4个成员都是int
# 常用结构已经在"${NSISDIR}\Examples\System\System.nsh"声明
System::Call "*(i0,i0,i0,i0) i.r1"
System::Call "User32::GetWindowRect(i$HWNDPARENT, i$1)"
System::Call "*$1(i.r4,i.r5,i.r6,i.r7)"
Callbacks
回调函数只是传递到函数并由它调用的函数。它们通常用于逐项传递可能较大的数据项集。例如,EnumChildWindows使用回调函数。由于 NSIS 函数不是常规函数,因此系统插件提供了自己的机制来支持回调功能。它允许您创建回调函数,并在每次调用回调函数时通知您。
使用 Get 和回调创建语法创建回调函数。由于您自己不会调用回调,因此应使用点省略参数的源。调用回调时,参数的目标将填充传递给回调的值。回调将返回的值由返回"参数"的源设置。应始终设置返回"参数"的目标,因为系统将通知您调用回调。
System::Get "(i .r0, i .r1) iss"
若要将回调传递给函数,请使用 k 类型
System::Get "(i .r0, i .r1) isR0"
Pop $0
System::Call "dll::UseCallback(k r0)"
每次调用回调时,字符串回调#(其中 # 是回调的编号)将被放置在返回"参数"的目标中。创建的第一个回调的编号为 1,第二个回调的号码为 2,第三个回调的号码为 3,等等。由于 System 是单线程的,因此只能在调用另一个函数时调用回调。例如,只有在调用 EnumChildWindows 时才能调用 EnumChildWindows 的回调。因此,您应该在可能调用回调的每个函数调用后检查回调# 。
System::Get "(i .r0, i .r1) isR0"
Pop $0
System::Call "dll::UseCallback(k r0)"
StrCmp $R0 "callback1" 0 +2
DetailPrint "UseCallback passed ($0, $1) to the callback"
处理回调调用后,应使用Call,将 Get 返回的值传递为Get - 回调。这将告诉系统从回调返回。在调用函数之前,必须清除返回"参数"的目标,以避免错误检测回调调用。如果在创建回调时为返回"参数"指定了源,则应使用相应的返回值填充该源。回调不会自动释放,不要忘记在使用完后释放它。
System::Get "(i .r0, i .r1) isR0"
Pop $0
System::Call "dll::UseCallback(k r0)"
loop:
StrCmp $R0 "callback1" 0 done
DetailPrint "UseCallback passed ($0, $1) to the callback"
Push 1 # return value of the callback
StrCpy $R0 "" # clear $R0 in case there are no more callback calls
System::Call $0 # tell system to return from the callback
Goto loop
done:
System::Free $0
注意事项:
1. 若要查找 COM 接口中成员的索引,需要在 Visual C/C++ 或平台 SDK 的标头文件中搜索此 COM 接口的定义。索引基于零。
2. 如果找不到函数或使用参数类型,则"A"或"W"将追加到其名称中,并再次查找。这是因为许多 Windows API 函数有两个版本,一个用于 ANSI 字符串,另一个用于 Unicode 字符串。函数的 ANSI 版本标有"A",Unicode 版本标有"W"。例如: lstrcpya 和 lstrcpyW 。
3. 系统 32 目录中的库无需路径即可加载。所有其他库都应加载一个引用的完整路径。
有效的选项
选项 | 意义 |
c | cdecl 调用约定(调用方还原堆栈)。默认情况下,stdcall 调用约定在 x86(由调用方还原堆栈)上使用 |
r | 始终返回 (对于 GET 意味着您应该弹出结果并处理,对于Call 意味着您应该至少弹出结果)。默认情况下,结果只返回错误(对于 GET,您将弹出错误结果和正确的处理,对于 CALL,您将在定义的返回位置获得返回或结果)。 |
n | 没有重新定义。每当使用此 proc 时,它永远不会被 GET 或 CALL 重新定义。此选项永远不会继承给子对象。 |
s | 使用常规堆栈。每当定义第一个回调时,系统开始使用函数调用的临时堆栈。 |
e | 在程序结束后调用GetLastError()并把结果Push到堆栈。 |
u | 调用后卸载DLL(例如,使用 FreeLibrary,以便您能够将其删除)。 |
2 Experimental v2 syntax
Experimental v2 syntax
大写结构类型内存对齐。小写类型结构内存不对齐。
基于分配的回调的回调 ID
Usage Examples
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
# call IActiveDesktop->GetWallpaper
System::Call "$0->4(w .r2, i ${NSIS_MAX_STRLEN}, i 0)"
# call IActiveDesktop->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位函数
• 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
常见问题
•问:如何将结构传递给函数?
答:首先,你必须为结构分配内存。有两种方法:使用 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
完整示例
# allocate
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)"
# free
System::Free $1
DetailPrint "Structure size: $2 bytes"
DetailPrint "Memory load: $3%"
DetailPrint "Total physical memory: $4 bytes"
DetailPrint "Free physical memory: $5 bytes"
DetailPrint "Total page file: $6 bytes"
DetailPrint "Free page file: $7 bytes"
DetailPrint "Total virtual: $8 bytes"
DetailPrint "Free virtual: $9 bytes"