NSIS3.08 System 插件中文帮助文档

 

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 是参数列表以及如何处理这些列表。 您可以在参数中传递数据,也可以从中获取数据。 参数列表以逗号分隔。 每个参数由三个值组合而成:typesourcedestinationType 可以是整数,字符串等。 Source,也就是参数值的源,可以是NSIS寄存器变量($0, $1, $INSTDIR), NSIS 堆栈,具体值(5、“test”等)或什么都没有(null)。 Destination是调用返回后参数值的目标,可以是一个 NSIS 寄存器变量,NSIS 栈或者什么都不需要输出。 如果不需要,sourcedestination 之一也可以是点 (`.')。

    RETURN 就像一个单一的参数定义,但 source 只在创建回调函数时使用。 通常 source 是一个点。

    OPTIONS 是控制 System 插件行为方式的选项列表。 每个选项都可以通过在前面加上感叹号来关闭。 例如:?!e

    PARAMSRETURNOPTIONS 可以在一个 Get/Call 行中重复多次。 这样重复的时候可以省很多事,只改自己想改的就行。 Typesource 和/或 destination 可以省略每个参数,甚至是返回值。 可以添加或删除选项。 这允许您定义函数原型并节省一些输入。 最后两个例子展示了这一点。

    PROC 也可以重复,但它必须以井号 (`#') 为前缀,井号前面有双冒号 (shell32::#18) 的情况下,它会被解释为函数序数。

    PROC 可能的值和含义

    含义例子
    DLL::FUNC DLL 导出的 函数 user32::MessageBox
    ::ADDR 地址为ADDR的函数 见下文
    *ADDR 地址为ADDR的结构 见下文
    * 新结构 见下文
    IPTR->IDX
    IPTR 指向的接口的
    成员索引IDX
    见下文
    <nothing> 新回调函数 见下文
    PROC Get 返回的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(仅限结构)

    此外,每种类型(bhk@ 除外)都可以使用星号作为前缀来表示 一个指针。 当使用一个星号时,System 插件仍然认为是参数的值,而不是指针的地址。 要传递直接地址,请使用不带星号的“p”。 一个使用示例Alloc 返回地址,因此它的返回值应该与 `p' 一起使用,不带星号。

    可以使用的源和目标

    类型含义
    . 忽略  
    number 具体的十六进制、十进制或八进制整数值。可以使用符号(“|”)对几个整数进行“或”运算
    'string'
    "string"
    `string`
    具体字符串值
    r0r9 分别为$0到$9
    r10r19
    R0R9
    分别为$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: 首先,您必须分配结构。 这可以通过使用具有特殊结构分配语法的 AllocCall来完成。 接下来,如果你需要在结构中传递数据,那么你必须用数据填充它。 然后使用指向该结构的指针调用该函数。 最后,如果你想从被调用函数写入的结构中读取数据,则必须在结构处理语法中使用 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

posted on 2022-12-31 20:37  水晶石  阅读(1006)  评论(0编辑  收藏  举报