Lisp+DWX 之二 Register 方法学习

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/yxp_xa/article/details/73406038

DynamicWrapperX 对象(暂时译为“动态包装器”,简称 DWX),目前支持 24 个方法,Register 是最主要的方法之一。该方法可以将 ActiveX DLL 里众多的函数一一引用出来,注册(绑定)为 DWX 对象的一个方法,这样使得 DWX 对象支持的方法几乎是无限的。例如 Windows 系统自带的一些 DLL 库提供了大量的 API 函数(大约上万个),皆可以用 Register 方法绑定而为我所用。
这些,我们需要感谢 Yuri Popov 所做的工作。


功能

Register 方法将一个 DLL 库中的函数注册为 DWX 对象的方法。
在 Vlisp 中创建 DWX 对象:

(setq DWX (vlax-create-object "DynamicWrapperX"))

语法及参数

Register 方法语法如下:

DWX.Register( DllName, FuncName [, i=ParamTypes] [, r=RetValType] )

其中: DllName 为 dll 库文件名;FuncName 为 dll 库包含的函数名;参数 i (input) 为输入参数类型;r (return) 为输出值类型,每种类型用一个小写字母表示。

通常有以下三种注册方式:

  • 按名称注册(文件名 user32.dll,函数名 MessageBoxW,有 4 个输入参数,1 个输出参数)
  DWX.Register("user32.dll", "MessageBoxW", "i=hwwu", "r=l")
  • 以不同名称注册
  DWX.Register("user32:BadName", "GoodName", "r=n", "f=t")
  • 按顺序注册
  DWX.Register("user32:110", "MethodName", "i=p", "r=l") '十进制序数
  DWX.Register("user32:0x6E", "MethodName", "i=p", "r=l") '十六进制序数

最常用的是第一种,记住:函数名和参数类型对大小写是敏感的,这点与 lisp 习惯有较大差异。
例如:在 Vlisp 中调用 API 延迟函数:

;;从 kernel32.dll 文件中导出 sleep 函数,并注册为 DWX 对象的方法
(vlax-invoke DWX 'Register "kernel32" "Sleep" "i=m")

;;调用 DWX 对象的 sleep 方法,其中 5000(毫秒)为输入参数
(vlax-invoke DWX 'Sleep 5000)

参数说明:

  • 如果库的文件具有 “.dll” 扩展名,则扩展名是可选的。如果文件没有扩展名,应该在它的名字后放一个点。例如:“mylib.”
  • 系统 DLL 是没有路径的。其它情况,取决于它们的注册路径。
  • 具有字符串参数或返回值的 Windows API 函数通常存在两种变体。
    例如 MessageBoxW 为 Unicode 字符串和 MessageBoxA 为 ANSI 编码。“A” 可以从名称中省略,因为如果找不到 MessageBox,DWX 会自动添加,但 “W” 不能省略。
  • 序数可以是十进制或十六进制。请记住,在不同版本的 DLL 和相同版本的 x86 和 x64 版本之间,不能保证序数相同。
  • 根据函数功能,标有前缀 “i=”,“r=” 和 “f=” 的参数不是必须的,并且它们的顺序是任意的,因为它们已经被前缀区分开。
  • “i=” 之后指定的字母定义输入参数的类型(整数,浮点数,字符串等)。只有当函数没有设计这个参数时,才能省略 “i=” 参数。
  • “r=” 之后的字符指定返回值的类型。无论函数是否返回任何东西,都可以省略它。(yxp注:最好别省略,否则一些 API 在 lisp 中会返回 nil)
  • “f=” 指定影响函数调用方式的标志。当前有两个标识被识别:“t” 表示函数的调用约定是 “thiscall”,而 “l”(最后一个错误)意味着在函数调用后必须立即调用 GetLastError API。可以从 LastError 方法看到返回的错误代码代表的意义。“t” 标志仅由 32 位版本的 DWX 使用, 64 位版本将被忽略。

输入参数和返回值

DWX 用“i=***”来表示输入参数和返回值的类型。各字母代表的数据类型意义如下:

m — 双长整型,默认是有符号的,大小 64 位(64 个二进制,8 字节),整数,有 LONGLONG 等
q — 无符号双长整型(无负整数),大小 64 位,整数,有 ULONGLONG 等
l — 长整型,大小 32 位,整数,有 LONG, INT, BOOL 等
u — 无符号长整型,大小 32 位,整数,有 ULONG, UINT, DWORD 等
h — 句柄,整数,包括 HANDLE, HWND, HMODULE, HINSTANCE, HICON, 其中 32 位系统占 32 个位, 64 位系统占 64 位
p — 指针,整数,对于数字,它与 u(x86)或 q(x64)相同,但也可以用于传递对象或字符串
n — 短整型,大小 16 位,整数,包括 SHORT 类型
t — 无符号短整型,大小 16 位,整数,包括 USHORT, WORD, WCHAR, OLECHAR 类型
c — 字符型,大小 8 位,整数,包括 CHAR
b — 无符号字符型,大小 8 位,整数,包括 UCHAR, BYTE
f — 单精度,大小 32 位,浮点数,包括 FLOAT
d — 双精度,大小 64 位,浮点数,包括 DOUBLE
w — Unicode 字符串,包括:BSTR, LPWSTR, LPOLESTR, OLECHAR , WCHAR
s — ANSI 字符串 (默认 codepage),LPSTR, LPCSTR, CHAR *, …
z — OEM/DOS 字符串 (默认 codepage) — LPSTR, LPCSTR, CHAR *, …
v — 指向变体结构的一个指针类型

说明

扫描二维码关注公众号,回复: 3346719 查看本文章
  • 除了句柄和指针之外,还有其他类型可以改变程序的位置。例如:LRESULT,LPARAM,WPARAM,SIZE_T。它们也应该被传递并返回为类型 h 或 p,所以您的代码可以正常工作,无论脚本解释器的位置如何。
  • 对应于 m 和 q 的脚本类型将是 VT_I8 和 VT_UI8,但它们不受 JScript 和 VBScript 的支持,这限制了在脚本中使用 64 位整数的方式。只要函数返回的值允许,动态包装器将其转换为类型 VT_I4(32 位有符号整数)或 VT_R8(双精度浮点数)。由于 VT_R8 的尾数只有 53 位,它不能表示 64 位整数范围内的每个数字。在这种情况下,返回 VT_I8 或 VT_UI8。您可以使用这些类型将其作为参数传递给其他方法,或通过 WScript.Echo 或 MsgBox 显示其值。
  • 当大整数作为 VT_R8 返回,并且要在消息框中查看其值时,可能无法正确显示,因为浮点数的字符串表示中的小数点后的位数有限。例如,数字是 9223372036854775808,在消息框中,您可能会看到 9,22337203685478E+18而不是9,223372036854775808E + 18。但是,变量中的实际数值不是四舍五入并且保持准确。
  • 如果 64 位的整数值不适用于任何可用的数字类型,则可以使用字符串格式,用十进制或十六进制(0x 前缀)指定参数。例如:
    DWX.Register(“lib.dll”, “func”, “i=m”)
    DWX.func(“0xFFFFFFFFFFFFFFFF”)
    DWX.func(“-0x7FFFFFFFFFFFFFFF”)
    DWX.func(“18446744073709551615”)
    DWX.func(“-9223372036854775807”)

输出参数

注意,输出参数并不全是函数返回值,DWX 函数并不依赖输出参数来获取返回值。用传址的方式,用输入参数可以接收返回值,详见说明和示例。

m — 指向指定数字的指针(其内存中的地址) — LONGLONG *, PLONGLONG
q — 同上 — ULONGLONG *, PULONGLONG, …
l — 同上 — LONG *, LPLONG, …
h — 同上 — HANDLE *, PHANDLE, LPHANDLE, …
u — 同上 — ULONG *, LPDWORD, …
p — 同上
n — 同上 — SHORT *
t — 同上 — USHORT , LPWORD, WCHAR , OLECHAR *, …
c — 同上 — CHAR *, …
b — 同上 — UCHAR *, LPBYTE, …
f — 同上 — FLOAT *, PFLOAT
d — 同上 — DOUBLE *, PDOUBLE
w — 输出 Unicode 字符串
s — 输出 ANSI 字符串
z — 输出 OEM 字符串

说明

  • 上面输出数据的类型可以用于脚本语言,通过引用将参数传递给 DWX 方法,就像 VBScript 所示。在这种情况下,动态包装器可以获取一个指向参数值的指针,然后将其传递给注册的函数,以便函数可以更改该地址的值。但是,如果参数通过值传递(与传址不同),就像在 JScript 中那样被复制,所以没有办法找到并更改原始文件。在这种情况下,您可以通过 MemAlloc 方法在内存中分配一个缓冲区,将缓冲区指针传递给函数(作为 p 类型),并且在调用该函数后用 NumGet 方法读取缓冲区中的数字。
  • 一些脚本引擎在将它们传递给方法之前也会复制字符串,在这种情况下,也没有道理使用字符串的输出类型。解决方案与上述类似:将一个内存缓冲区以类型 p 传递,并通过 StrGet 方法从其读取生成的字符串。

示例

用 Vlisp 获取鼠标在 Windows 窗口任何位置的坐标。(本示例得到了 highflybird 的指点,在此表示感谢)
本例的难点在于数据的类型,GetCursorPos 的返回值是一个包括 xy 坐标的 type 类型,在 VB 中可以轻松定义这样一个类型,但是 lisp 语言中并未提供 type 数据类型,这时候就要用缓冲区指针来接收。

;;API GetCursorPos 函数声明
;;输入参数 "i=m" 用来接收坐标返回值;测试电脑的屏幕分辨率为 1920×1080 ,
;;目测 4 个字节足够表示一个坐标,4 个字节可表示的数字范围:-2^15~2^15-1
;;实际并非目测而是通过 MemRead 读取缓冲区返回值一看就知道了。
;;输出参数 "r=m", 1 为成功;0 为失败,并且设置 GetLastError
(vlax-invoke wrap 'Register "user32" "GetCursorPos" "i=m" "r=m") 

;;分配 16 个字节的缓冲区,其实 8 个字节也够了,因为输入参数的 m 类型只占 8 字节
;;但是咱有 16G 的内存,需要这么节约吗?
(setq pt (vlax-invoke wrap 'MemAlloc 16 1)) 

;;pt 是一个地址指针,指向 16 个字节的缓冲区,用传址的方式交给函数 GetCursorPos
(vlax-invoke wrap 'GetCursorPos pt) 

;;现在缓冲区内应该有东东了,用 NumGet 方法可查看
;;一个屏幕 x 坐标刚好占 4B,是 "l" 类型,再跳过 4 个字节可以读到 y 坐标。
(setq x (vlax-invoke wrap 'NumGet pt 0 "l")) 
(setq y (vlax-invoke wrap 'NumGet pt 4 "l")) 

;;释放缓冲区
(vlax-invoke wrap 'MemFree pt) 

猜你喜欢

转载自blog.csdn.net/yxp_xa/article/details/73406038
今日推荐