DllCall的使用方法Q版解说 —— 飞跃
(Q版解说是为了给新手解惑,DllCall的基础知识请先学习AHK中文帮助文件)
1、我是一个AHK程序,居住在地球之外某个星球(硬盘)中,有一天某人双击了我,
我跳了起来说:Windows系统大神,我要运行,我要发威,我要表现了!
系统大神把我接到地球(内存)中,给我画了个地盘——中国,说:这是你的私人空间,
其他地盘你不准去访问。
2、我本身有许多法宝(命令或函数),但是有时觉得不够用,所以想使用系统大神
早就准备好的法宝(WinAPI函数),它们放在系统大神打包好的Dll包裹中(Dll文件),
所以我呼叫了DllCall宝宝来使用系统大神Dll包裹中的法宝:
系统大神的Dll包裹最初也放在某个星球(硬盘)中,DllCall宝宝把它拉进地球(内存)
的中国区域,这里是我的地盘,然后通过法宝的名字(函数名)找到了法宝,准备使用。
3、但是使用系统大神的法宝有点麻烦,它有许多个触须,每个触须需要输入一个数字
作为参数,而且触须有长有短,看起来好复杂。DllCall宝宝对我说,你把每个参数的
数字和参数的说明类型告诉我,我来帮你把数字输入到触须中就行了。我说要提供参数
的数字我懂,但是为什么还要告诉你类型呢?每个数字分配给一个触须不就好了吗?
看我还不明白,DllCall宝宝只好向我详细介绍起来,为什么需要参数类型:
第一,系统大神原来的核心装备(CPU)比较差,处理数据一次只能读写32位开关构成的
数字(8个开关也叫一个字节所以是4字节),所以法宝制作工厂(编译器)制作的法宝,
上面的触须每个的长度一般是4字节,有时候某个触须4字节的可操作数字太小就只好加长
一倍为8字节长度,所以法宝上的触须长度可能不一致。后来系统大神的装备(CPU)
升级了,每次可以读写64位开关构成的数字(即8字节),法宝工厂(编译器)制作的
法宝,上面的触须每个的长度统一为8字节,这时候所有的法宝上的触须都一样长了。
所以如果我自身的AHK程序是32位版,操作的Dll包裹版本也必须为32位版,我会统一把
每个你给的数字自动扩充到4字节再传输给触须,但是如果某个触须长一些,说明需要
8字节的数字,你不特别跟我说明(Int64)类型,我就不会扩充到8字节再传输给触须,
就会出错。当然如果我自身的AHK程序是64位版,操作的Dll包裹版本也必须为64位版,
我会统一把你给的数字扩充到8字节传输给触须就不会出错了。在64位系统的兼容模式中
运行32位版的AHK程序,系统发来的Dll包裹其实是32位的,但如果使用其他人制作的Dll
包裹时就要注意Dll包裹的版本必须与AHK程序的版本一致,参数自动扩充的位数才匹配。
第二,如果传输的数字是需要操作小数,或者需要的数字大于8字节所能表示的大小,
又怎么办呢,这个时候就需要用浮点数来代表。浮点数是一种指数形式的数字描述方式,
可以表示很巨大的数字。它是一种设计精巧的整数,看起来是整数,但是前面的某些位
表示尾数,后面一些位表示指数,所以它的含义是不等于表面的整数的,如果这个法宝
的触须需要一个浮点数,如果你不跟我特别说明(Float或Double)类型,我就会按普通
的整数(舍弃小数部分)传输,而不会构造出浮点数表示的特殊整数来传输,就会出错。
前面这两个原因是必要的原因,还有两个参数类型是我为了方便你的使用特别添加的,
虽然不是必要的原因,但是可以由我来帮你自动完成一些操作,让你省很多事。
第三,我提供了Str类型可以方便你输入字符串。你要知道很多法宝不仅可以处理数字,
还可以处理字符串。字符串很长,所以参数是字符串的门牌地址(内存首地址)。这个
地址的值在32位系统中是4字节,在64位系统中是8字节,你当然可以自己取得门牌地址
的值交给我(用&取变量的内存地址),我会自动帮你扩充好再输入触须。但是你使用
Str参数类型告诉我的话,有几大好处:字符串有方言(ANSI编码)和官话(Unicode
编码)两种,系统大神的法宝一般也同时提供这两种语言的法宝,以A和W作为后缀来
区别。你本人的出身要么说土话(ANSI版),要么说官话(Unicode版),如果不巧你
选择了语言不匹配的法宝,就需要你自己手动转换语言,再把转换后的字符串的门牌
地址的数字交给我,这就比较麻烦。这时你可以使用AStr类型让我帮你把你自身语言的
字符串自动转换为土话(ANSI版)给接受土话的法宝,或者使用WStr类型帮你自动转换
为官话(Unicode版)给接受官话的法宝。还有一种普通的Str类型,虽然不会帮你自动
转换语言,但是可以帮你自动更新字符串的长度。你自身使用的所有字符串都带有一个
长度标记,免得每次使用这个字符串都要统计长度,但是当你把字符串的门牌地址传给
法宝后,法宝可能在这个门牌地址后面写入新的字符串(输出字符串),这个新的字符串
可能更长或更短,这个长度改变了你却不知道,你还要手动更新一下字符串的长度才能
使用,否则使用原来标记的长度就有可能出错。用Str类型我可以自动帮你更新长度标记。
另外,使用Str类型可以直接使用带双引号的原义字符串或变量,而不用手动取变量的
门牌地址数字,由我来帮你取地址,也省了你的手动使用&取变量地址。
这里有个小技巧,虽然系统大神提供的操作字符串法宝一般都提供了A和W结尾的两种
版本,但是你告诉我法宝名称的时候最好不用加A和W这个后缀。我会根据你本人的出身
是说土话(ANSI版)还是说官话(Unicode版),自动帮你加上A或W这个后缀来找法宝,
这样找到的法宝就刚好匹配了。当然你给的法宝名称能直接找到的话就不会去加后缀了。
第四,我提供了*类型(也可用P类型表示)可以方便你通过参数变量返回单个数字。
法宝不仅可以输入数据,还可以输出数据,一是通过法宝的出口返回一个返回值数字,
二是通过参数指示的门牌地址把数据写入门牌地址起始的空间中。如果要输出很多数据,
可以是字符串的首地址或者数据结构的首地址(数据结构是约定好偏移的一系列数值),
以后可以手动从字符串的首地址读取新的字符串,或者从数据结构的首地址读取某个偏移
的数值,这种情况只能手动输入门牌地址。但是往往法宝要往某个参数的地址中输出一个
数字即可,这种情况很常见,这时使用*类型可以由我帮你操作了。我先把你的参数变量
的数字写入某个门牌地址(内存地址)中,然后把这个门牌地址传输给法宝,法宝内部
把新的数字输出到这个门牌地址中,然后我把这个门牌地址中的新数字读取出来赋值给
你的参数变量,就省了你很多事。否则不用*类型的话,虽然只是通过参数地址返回一个
数值,你也要像操作数据结构一样,手动把参数的数字写入某个变量的门牌地址(使用
NumPut写入数据结构),然后手动输入变量的门牌地址(用&取址),最后法宝执行完
后还要手动从这个门牌地址读取数字(使用NumGet读取数据结构),这就相当麻烦。
另外参数类型我还增加了Ptr类型,它自动适应我自身的AHK程序是32位还是64位的,如果
是32位的就等效于4字节(Int类型),如果是64位的就等效于8字节(Int64类型),这个
类型在数据结构中很有用,但是在法宝的参数类型中意义不大,因为1字节(Char类型)、
2字节(Short类型)、4字节(Int类型)的参数我都会自动扩充为Ptr类型再传输给触须。
4、哦,我懂了为什么要使用参数类型了,原来有两个必要的原因和两个带来好处的原因。
本来我看系统大神的法宝那些参数声明,花里胡哨,一头雾水,现在明白了,要么是传输
数值的(用Int、Int64、Float、Double类型),要么是传输门牌地址的,门牌地址的
还有两种特例,一是输入输出字符串的用Str可以方便操作,二是通过参数变量返回单个
数字的用*类型也可以方便操作,将法宝的参数声明按这个用途分类,就万变不离其宗了。
5、DllCall宝宝又说,参数类型其实不难,你一下子懂了也不奇怪,有点难的是数据结构。
其实Char、Short、Int、Int64、Ptr这些都是主要用于数据结构的,数据结构是一种从
数据结构的门牌地址(内存首地址)开始,约定(声明)好的多个按偏移距离输入数字的
一段空间,只要知道门牌地址和偏移距离,就可以读取和写入数字了。数据结构中的各个
有含义的字段,位于哪个偏移需要计算,看起来不难,但是一要考虑Ptr地址类型是自适应
的类型可能有两种值,二是结构中可能包含一段特定长度的字符串,如果是土话(ANSI
版)是占据长度个字节,如果是官话(Unicode版)则是占据长度的两倍字节,三是系统
为了读取数据方便,要求数据结构每个字段的存放位置相对于门牌地址的偏移必须对齐到
本身的类型长度的整数倍,比如某个字段是8字节,前面的那些字段已经占用了3个字节,
它就不能从偏移3个字节后开始,必须从偏移8字节后开始存放,所以计算起来就要小心。
另外数据结构的总大小必须为最大的字段类型的整数倍,所以计算大小时又要注意这一点。
6、最后还有一点,你传输字符串或者数据结构的门牌地址时要注意它们已经申请的空间
大小是否符合法宝操作的要求。因为你使用的普通变量是程序自动管理大小的,是尽可能
少占空间的,比如没用过的变量占用空间为0,你可以用&取它的门牌地址但它申请的空间
为零,法宝操作这个门牌地址后面的空间,写入数据就可能破坏你的其他空间数据,所以
输入门牌地址给法宝操作,一定要事先给变量申请足够空间(用VarSetCapacity申请),
然后就不怕了,不论法宝是输出字符串还是修改数据结构中的数据,都不会超出边界了。
听了DllCall宝宝的这些讲解,我终于对使用系统大神的法宝(WinAPI函数)游刃有余了。