Lisp+DWX 之三 DWX 方法分类

DynamicWrapperX (简称 dwx),作者: Yuri Popov (俄罗斯), yurip42@gmail.com,学习使用 dwx 之前,让我们在心里默默的感谢他,感谢他出色的工作,为我们带来一款优秀的工具。
DWX 优点:身材极小——不足 30 kB,压缩后 12 kB,方便嵌入;功能强大——可导入 Windows API 函数,执行汇编代码;兼容性好——各种脚本语言都可以轻松使用 JS,VS,Lisp…
文本将 dwx 的 23 个方法在 AutoCAD x64 环境中用 Lisp 语言进行测试,其结论不一定正确,如果聪明的读者发现本文的测试结果有误,请一定告诉我,9034598@qq.com,非常之感谢。


一、DWX 方法

1.1 对象创建

启动 AutoCAD 后,在命令行运行下面 Lisp 代码,将创建一个 dwx 对象:

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

dwx 没有做 COM 类型说明接口,用 (vlax-dump-object dwx t) 查不到 dwx 对象的属性和方法。但并不影响我们对他的使用,因为作者随 dwx 一起发布了方法的帮助说明。详见:
http://dynwrapx.script-coding.com/dwx/pages/dynwrapx.php?lang=en

1.2 方法分类

从 Yuri Popov 先生给出的帮助文件看出,DWX 2.1.1 版支持 23 个方法,根据这些方法在 AutoCAD 中的测试结果,本人试着将 dwx 方法分为以下几类:

  • 主要方法 Main Methods: 不可或缺的方法,可实现 dwx 的主要功能
  • 一般方法 General Methods: 可以让 dwx 更加完美、高效的工作
  • 可用方法 Available Methods: 即使不使用,不会影响 lisp 功能实现
  • 用途不明 Unidentified Purpose: 因本文作者水平有限,在 CAD 中没测试出应该如何使用
  • 不用方法 Impracticable Methods : 经过测试,在 CAD 中无效的 dwx 方法

1.2.1 主要方法

序号方 法功 能
1Register函数(DLL)注册
2MemAlloc分配内存并返回它的指针
3MemRead从内存读出二进制为 hex 字符串
4MemWrite将字符串 hex 写入内存并转为二进制

1.2.2 一般方法

序号方 法功 能
1StrPtr返回一个字符串的指针
2StrGet在指定地址读取字符串
3StrPut将字符串写入指定的地址
4NumGet从内存读出数字
5NumPut数字写入内存
6MemFree释放地址指向的内存

1.2.3 可用方法

序号方 法功 能
1RegisterCode执行汇编代码
2MemZero用 0 填充内存
3MemCopy两块内存间复制
4Space创建一个指定长度的字符串
5Version查询 DWX 的版本

1.2.4 用途不明

序号方 法功 能
1RegisterCallback注册一个回调函数
2RegisterAddr用内存地址注册一个函数
3ObjPtr返回对象的指针
4ObjGet根据指针返回对象
5VarPtr返回指向变体结构变量的指针

1.2.5 不用方法

序号方 法功 能
1Bitness查询DWX位数32/64
2LastError返回回调函数的一个错误
3ArrPtr返回安全数组的指针

二、DWX 方法测试

先将以上五个分类的方法测试结果发布如下:

2.1 查询位(不用方法)

方法: Bitness()
功能: 查询 DWX 的位数
示例: (vlax-invoke dwx 'Bitness) 返回 64
说明: x64 位 CAD 系统中自然注册 64 位的 dwx,实测无法注册 32 位 dwx。
那么,既然都注册 64 位 dwx 使用了,还能返回 32 吗?

2.2 函数声明(主要方法)

方法: Register( DllName, FuncName [, i=ParamTypes] [, r=RetValType] )
功能: 将 DLL 中函数绑定为 DWX 对象的方法。
详见: Lisp+DWX 之二 Register 方法学习

2.3 回调函数注册(用途不明)

方法: RegisterCallback( FuncRef [, i=ParamTypes] [, r=RetValType] )
功能: 注册一个回调函数,FuncRef 按址引用的函数名,i 参数类型,r 返回值类型。
说明: 暂时没搞懂这个函数的功能。

2.4 地址注册函数(不用方法)

方法: RegisterAddr(Address, "FuncName", "i=hwwu", "r=l")
功能: 通过内存地址注册一个函数,其参数与 Register 的参数相同
说明: 既然已经用 Register 注册了,还需要用这个函数再注册?
本着折腾的精神,我们试试 lisp 函数是否可以被这个函数注册。
下面我写了一个 getPtrs 获取 lisp 函数的指针的函数,再用 RegisterAddr 注册

;;CAD 位数判断,如果是 64 位返回 t,否则返回 nil
(defun isACAD64 ()
(= 17(strlen(vl-prin1-to-string
    (cadr (read (strcat "(" (vl-prin1-to-string +) ")"))))))
)
;;;;查询任意 lisp 函数的指针, 示例: (getPtrs setq)
(defun getPtrs (cm / hex ss m n sum)
(setq hex '(("F" . 15)("E" . 14)("D" . 13)("C" . 12)("B" . 11)
    ("A" . 10)("9" . 9)("8" . 8)("7" . 7)("6" . 6)("5" . 5)
    ("4" . 4)("3" . 3)("2" . 2)("1" . 1)("0" . 0)))
(if (= 'SUBR (type cm))(progn
    (setq ss (substr (vl-prin1-to-string cm) 9 (if (isACAD64) 16 8))
        ss (strcase ss)
        n (strlen ss)
        m 0 sum 0)
    (while (> n 0)
    (setq sum (+ (* (cdr (assoc (substr ss n 1) hex)) (expt 16 m)) sum) n (1- n) m (1+ m))) sum
    )
)
)

测试 lisp 函数,下面的函数只返回一个数字, (test) 将返回 335
(defun test()(+ 100 235))
下面函数返回 test 函数的指针为 838496728
(setq Tpn (getPtrs test))
用 RegisterAddr 函数注册试试,将 test 绑定为 dwx 的一个方法
(vlax-invoke dwx 'RegisterAddr tpn "testx" "r=l")
竟然注册成功!
再用 dwx 调用注册的 lisp 函数:
(vlax-invoke dwx 'testx)
哈,这下 DWX 被玩坏了,CAD 直接崩溃退出。
看来虽然都是函数,但是编码机制还是有差异。lisp 代码加载后 CAD 将其解释为二进制可执行编码,但和原生的 dll 函数还是有区别的,毕竟前者只需要在 CAD 环境中生存。
当然,还有一种可能就是 getPtrs 函数的思路完全是错的,我们并没有真正获得 test 函数的指针。

2.5 执行汇编(可用方法)

方法: RegisterCode(Code, "Multiply", "i=ll", "r=l")
功能: 将机器码(第一参数)以十六进制字符串的形式进行处理(汇编)
说明: 这个函数很强大,可用执行汇编代码,但研究 x64 位汇编门槛有点高,所以本人将该方法归位可用方法,实际上该方法可用实现一些强大的功能。
dwx 作者给出范例如下:
代码以二进制形式写入被分配内存,然后可以用第二参数中指定的名称调用它。该方法返回代码的地址。
机器码在 x86 和 x64 里是不同的,因此需要指出使用的是哪个版本的代码。
机器代码功能的一个可能应用是处理大量数据,其中机器代码的运行速度可能比脚本代码快几十倍至几百倍。
如果您想使用地址的代码,而不创建一个新的方法,您可以省略所有参数,除了第一个参数。
但是请记住,分配给代码的内存仍然与对象相连,如果对象被释放,它将被释放。

(defun hexcode()
    (setq DWX (vlax-create-object "DynamicWrapperX"))
    (setq Code "4889C8 48F7EA C3");;for x64 乘法的汇编代码 
    (setq CodeAddr (vlax-invoke DWX 'RegisterCode code "Multiply" "i=ll" "r=l"))
    (vlax-invoke DWX 'Multiply 5 4) ;;执行 4*5
)

代码字符串可以是连续的,或由空格、制表符、换行符组成。可以在字符串中插入注释,用括号括起来。如下:

(setq Code "4889C8 (mov rax,rcx) 48F7EA (imul rdx) C3 (ret)")

如果字符串有多行,你可以标记分号来注释。示例如下:

    (setq Code (strcat
        "4889C8 ; mov rax,rcx\n"
        "48F7EA ; imul rdx\n"
        " C3 ; ret"
    ))

但需要注意的是,在这种情况下,每一行(除了最后一个)的最后必须有一个换行符作为注释的结尾标记。

2.6 数组指针(不用方法)

方法: ArrPtr( Array )
功能: 返回一个安全数组的指针
说明: lisp 似乎无法使用这个函数
测试:
声明一个一维安全数组,从 0 - 3 的字符型一维数组,并赋值
(setq Astr (vlax-make-safearray 8 '(0 . 3)))
(vlax-safearray-fill Astr '("abcd" "1234" "你好" ""))
在获取数组指针时,将返回一个错误:
(vlax-invoke dwx 'ArrPtr Astr)
返回:Argument type can’t be coerced into the parameter type
(不能强制转换为参数类型)所以这个函数对 lisp 无效。

2.7 查看错误(不用方法)

方法: LastError( [Flag] )
功能: 返回函数调用的一个错误信息。
说明: 更多的时候 lisp+dwx 出错后,会直接导致 CAD 崩溃,这时候,怎么查看错误?

2.8 分配内存(主要方法)

方法: MemAlloc( Bytes [, ZeroMem] )
功能: 分配内存并返回内存块的指针
参数: Bytes - 内存块的大小(以字节为单位) ZeroMem - 如果设置为 1,内存将以二进制零填充。
示例: (setq nP (vlax-invoke dwx ‘MemAlloc 20 1))
说明: 在调用 API 函数时,接收与传回的参数,全靠这个函数来分配内存,进行指针传送。

2.9 释放内存(一般方法)

方法: MemFree( MemPtr )
功能: 释放分配的内存,参数为内存指针
返回: 成功则返回 nil
示例: (vlax-invoke dwx 'MemFree nP)

2.10 内存归零(可用方法)

方法: MemZero( Address, Bytes )
功能: 将分配的内存归零,新分配的内存有参数可归零,这个函数可以用来擦除已存在的内存块
说明: 内存块不用时直接释放,用的时候再分配,何需擦来擦去

2.11 内存复制(可用方法)

方法: MemCopy( SrcAddr, DestAddr, Bytes )
功能: 从源地址 SrcAddr 复制到目的地址 DestAddr, 一共 Bytes 字节
返回: 最后一个写入字节地址

2.12 内存读出hex(主要方法)

方法: MemRead( Address, Bytes [, BytesPerGroup] [, GroupsPerLine] )
功能: 从分配的内存中读出二进制信息,并以 16 进制的字符串显示
参数: Address 内存块的地址
Bytes 读取的字节数
BytesPerGroup 每个组中的字节数
GroupsPerLine 每行的组数
示例: (vlax-invoke DWX 'MemRead CodeAddr 8 3)
返回: “4889C8 48F7EA C300” 这就是我们在 No.5 函数中输入的乘法的汇编代码
用这个方法,我们可以获取所有载入内存的程序的汇编代码,只需要知道它的指针,甚至不限于 CAD 环境。

2.13 hex写入内存(主要方法)

方法: MemWrite( HexStr, DestAddr [, Bytes] )
功能: 将一个字符串(不需要指针),写入内存地址
返回: 返回值将是最后一个写入字节之后的地址。
如果指针为 0,则该方法返回十六进制字符串的二进制表示所需的缓冲区的大小(字节)
如果字符串包含空格等,将不会被写入,这可能很有用。
示例:

(setq nP (vlax-invoke dwx 'MemAlloc 8 1))   ;;分配一块内存
(vlax-invoke dwx 'MemWrite "4889C848F7EAC3" nP)  ;;写入 hex
(vlax-invoke dwx 'MemRead nP 7)

2.14 数字写入内存(一般方法)

方法: NumPut( Var, Address [, Offset] [, Type] )
功能: 写入数值到内存中。Var,要写入的值,变量的内存地址,内存偏移,类型。
返回: 写入的字节后的内存地址,例如 nP 是 100 写入 4 字节的变量后,则返回 104
示例: (vlax-invoke dwx 'NumPut 2 nP 0 "l")

2.15 内存读出数字(一般方法)

方法: NumGet( Address [, Offset] [, Type] )
功能: 从内存地址中获取数值。
参数: Address 指针,offset 偏移量,
能用于循环读写一系列的数值。Type,数值类型,默认”l”,即长整数,只能小写字母。
返回: 读出地址中的内容
示例: (vlax-invoke dwx 'NumGet nP 0 "l")

2.16 对象指针(用途不明)

方法: ObjPtr( Object )
功能: 返回一个对象的指针。
示例: (setq dwxPt (vlax-invoke dwx ‘ObjPtr dwx))
说明: 知道这个对象的指针后,只能查询它的二进制编码,而无法调用设置其属性,因此,在 AutoCAD 中用途不明。

2.17 对象指针检索(用途不明)

方法: ObjGet( ObjPtr )
功能: 通过指针检索对象,ObjPtr 为对象的指针
示例: (vlax-invoke dwx 'ObjGet dwxPt)
说明: 调用对象的指针,结果又返回了一个 dwx,用这个功能似乎可以将对象用数字传递和保存,暂时没想到其他实用价值。

2.18 字符串指针(一般方法)

方法: StrPtr( Var [, Type] )
功能: 得到一个字符串的指针(实际也是创建一个指针的方法,不需要专门分配内存)
var 字符串变量或者常量
type 是类型,”w”(默认方式),”s”,”z”
示例: (setq bb "hello!" aa (vlax-invoke dwx 'StrPtr bb "w"))
返回: 791641976 (指针)

2.19 内存读取字符串(一般方法)

方法: StrGet( Address [, Type] )
功能: 读取某个指针的值,Address地址,type类型。
示例: (setq ss (vlax-invoke dwx 'StrGet aa "w"))
返回: “hello!”
值得注意的是,字符串在内存中保存时,以一个字节的 0H 编码结束,所以如果需要分配内存来接收字符串,则内存的长度应该至少比字符串多一个字节,否则可能产生错误。

2.20 字符串写入地址(一般方法)

方法: StrPut( String, Address [, Offset] [, Type/Codepage] )
功能: 将字符串写入指定的地址。
返回: 该方法返回字符串的空终止符后的地址。如果将地址设置为 0 (偏移也为 0 或省略),
则该方法将返回目标字符串所需缓冲区的字节大小,包括终止 null 字符(1 个字节)。
示例:

(setq nP (vlax-invoke dwx 'MemAlloc 15 1))   ;;分配一块内存
(vlax-invoke dwx 'StrPut "hello world." nP 0 "s")

重要提醒: 虽然该函数语法中,地址偏移和字符类型可以缺省,但是在中文系统中,不要这么做,将参数写齐全。测试省略参数后将会导致 CAD 不稳定,有时候崩溃退出。

2.21 填充字符串(可用方法)

方法: Space( Count [, Char] )
功能: 创建指定长度指定字符的字符串。
参数: Count,数量,Char,指定字符,如果没这个参数,指定字符就是空格。
示例: (setq bb (vlax-invoke dwx 'Space 10 "a"))
返回: “aaaaaaaaaa”

2.22 变体指针(用途不明)

方法: VarPtr( Variable )
功能: 返回指向变体结构变量的指针
示例:
创建一个 “abcd” 的字符型变体
(setq StrVar (vlax-make-variant "abcd" 8))
返回该变体的指针
(setq StrPt (vlax-invoke dwx 'VarPtr StrVar))
我们来折腾一下这个指针:
按字符读取指针,得到一堆乱码: “X\005菝?”
(vlax-invoke dwx 'StrGet StrPt "s")
按变量读取指针,得到一堆没用的数字: -1008925352
(vlax-invoke dwx 'NumGet StrPt 0 "l")
按二进制读取 10 个字节,得到 “5805DDC3F97F000090B4”
(vlax-invoke DWX 'MemRead StrPt 10)
按对象读取变体指针,CAD 崩溃退出
(vlax-invoke dwx 'ObjGet StrPt)
所以还是那个问题,在 lisp 里我们得到一个对象、数据、函数的指针,有什么用? 它的汇编代码我们都不知道,AutoDesk 也没有公开相关技术文档, dwg 格式的编码也没公布。

2.23 版本查询(可用方法)

方法: Version( [Field] )
功能: 查询DWX的版本号
返回: DynamicWrapperX的四部分版本号的指定字段。
字段—将其设置为下表的适当值。如果字段被省略,它等于0。
假设完整版本是 2.5.7.10。
0 - 版本字符串: 2.5.7.10
1 - 主要版本号: 2。
2 - 小版本号: 5。
3 - 构建数字: 7。
4 - 修订号: 10 (0xA)
5 - major + minor: 0x20005。
6 - build +修订号: 0x7000A。
7 - 完整版本号: 0x200050007000A。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yxp_xa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值