发表回复 
DllCall的使用简介
2016-06-07, 01 : 31 (这个帖子最后修改于: 2018-01-02 11 : 00 by feiyue.)
DllCall的使用简介
;--------------------------------
; DllCall的使用简介 By FeiYue
;--------------------------------

DllCall是AHK的一个强大功能,用来调用Dll文件中的函数,例如标准的WinAPI函数。
许多新手可能觉得DllCall很复杂、很难用,于是对强大的WinAPI函数不敢上手,
只能羡慕那些高手们调用WinAPI实现各种功能。其实DllCall的使用并不难,下面我就
为大家拨开DllCall的神秘面纱,其实你也能简单学会!(下面都以WinAPI函数为例)

DllCall调用的格式为:
Result := DllCall("[DllFile\]Function" [, Type1,Arg1, Type2,Arg2, "Cdecl ReturnType"])

我们都用过AHK的内置函数或自己写的函数,比如:Pos:=InStr("abc123", "abc")
假如一个Dll文件中也有一个InStr函数,而且那个函数也需要两个字符串参数,怎么调用呢?
调用的格式为:Pos:=DllCall("Dll文件\InStr", "Str","abc123", "Str","abc", "Int")

我们比较一下区别:
AHK函数的函数名InStr,相当于DllCall调用中的 DllCall("Dll文件\InStr" 部分;
AHK函数的两个参数,在DllCall调用中每个参数前面都伴随了一个说明参数类型的参数;
最后DllCall调用的尾部还多出了一个说明函数返回类型的参数。
函数名部分很容易照搬使用,函数返回类型也只有两三种比较容易,只有参数类型有点难度。

要学会并自由使用DllCall需要进修三步:

第一步,要学会准确设定参数类型,这是重点、难点。

第二步,要学会用VarSetCapacity()分配内存。当我们使用一个新变量a并赋值时,
AHK自动为a变量申请了一块内存,但大小是尽量小的,不由我们掌控。
因此我们可以利用VarSetCapacity(a,字节数)来自己申请指定大小的内存。
要了解&为取变量内存地址的操作符,及用NumGet()、NumPut()读写内存数据。

第三步,了解字符串在内存中的编码状态,要学会用StrGet()、StrPut()转换字符串编码。
利用StrPut可以将原生编码(由AHK是ANSI版还是Unicode版决定的)
的字符串转换为目标编码,StrGet读取目标编码转换为当前的原生编码。

要明白AHK将数字也保存为字符串格式。我们把数字赋值给变量,例如a:=1,
假如把1的数值直接保存在a变量对应的内存地址中,那么就要区分变量
是占1个字节(Char),还是2个字节(Short),还是4个字节(Int),
还是8个字节(Int64),这样的话,每个变量使用前都必须先定义它的数据类型
(指明占用字节数)了,比如C语言就要用“int a;”定义一个4字节的变量。
作为脚本程序,为了方便用户免声明任意使用变量,所以AHK把数字都
保存为字符串格式,因为字符串的末尾都有“\0”作为结束标记。
(Ansi编码“\0”为1个字节值0,Unicode编码“\0”为2个字节值0)

第二、三步看帮助文件就很容易懂了,第一步我慢慢讲解,引导大家深入理解参数类型。

一、为什么每个参数前面都要设定参数类型?

1、DllCall调用的大概流程是:DllCall首先把Dll文件整个读取到AHK进程的私有内存中,
然后通过函数名字符串找到对应的函数入口地址,然后把参数一个一个压入AHK进程的栈中,
然后跳转到函数的入口地址,控制权交给了入口地址的机器码手中。机器码会从栈中
把参数的数值一个一个读取回来,然后执行自身的代码。机器码执行完毕后,会把返回值
写入通用寄存器EAX中,然后把控制权归还给AHK,然后AHK从通用寄存器EAX中读取返回值。

2、我们知道AHK脚本是动态解析的,AHK函数的参数都用逗号作为分隔符隔开,不用考虑参数
的数据类型,而DllCall调用函数,要把参数的数值连续压入栈中,中间可是没有分隔符的,
各个参数怎么准确分隔开就是个难点。

各位首先要理解的是,传给机器码的每个参数都是一个最多8字节的数值(整数或浮点数),
字符串是把字符串在内存中的首地址压入栈中,数据结构也是把内存首地址压入栈中,
简单数字和地址值就更是数值了。

3、如果所有机器码对应的WinAPI函数,参数都用固定的数值类型,比如Win32位系统都用
4字节的整型Int来接收,Win64位系统都用8字节的Int64来接收,那么这样固定约定好后,
AHK传递参数时就不需要说明参数类型了,每个参数压入栈中都占用对应字节数就好了。

WinAPI的参数很多都是内存地址,比如字符串是传入字符串的内存首地址,
数据结构也是传入内存首地址等等,而内存地址在Win32位系统中是占4字节,
在Win64位系统中是占8字节,刚好符合前面的固定约定。

但是WinAPI的参数也有一些是简单数值,比如长、宽、颜色值等等,取值范围并不是很大,
在Win64位系统中本来可以用4字节的整型Int就行了,但是固定用8字节就浪费了栈的空间,
而在Win32位系统中如果需要输入一个8字节的整型Int64参数,但是固定用4字节就麻烦了。

另一个难解的问题是,如果固定用4字节的Int或8字节的Int64,假如要输入4字节的Float
或8字节的Double类型的浮点数,就没有办法实现。因为浮点数在内存中的保存形式与
一般的整型数形式不同,是一种复杂的形式,浮点数不能以整型数的形式压入栈中。

4、所以编写WinAPI函数的程序员不会为了调用者的方便来个固定约定,该用地址型就地址型
(这在AHK中对应Ptr类型,会根据AHK自身是32位版还是64位版自动为4字节或8字节),
该用整型就整型(这在AHK中对应Int占用4字节),该用浮点类型就浮点类型(对应Float),
然后函数发布时会声明各个参数的参数类型(表面上五花八门,实际上是为了说明每个参数
在栈中占用的字节数,基本上就是地址型和整型两种)。

那么调用者就要迁就函数编写者的声明了,DllCall调用时就要按照WinAPI函数的声明,
在每个参数前面加上合适的参数类型,参数的数值压入栈中时,该占8字节就占8字节,
该占4字节就占4字节,这样WinAPI函数读取的时候根据函数声明来读取就不会出错了。
如果参数类型错误,机器码从栈中读取参数时就会错位读到错误的值,容易造成崩溃。

二、为什么AHK的参数类型不只用Ptr和Int两种?

我前面说过,WinAPI函数的参数数据类型,表面上五花八门,实际上基本上就是地址型
Ptr(视AHK是32位版还是64位版自动为4或8字节)和整型Int(4字节)两种,
因为这是编程中表示数值的最常用类型。(偶尔有参数为浮点数类型是特例)

如果哪位奇葩的程序员为了节约一点点的栈空间,在传入简单数值时,用到了16位整型
Short(2字节)和8位整型Char(1字节),那我真是服了他了,这是非常罕见的。

因此,我们不是可以用Ptr和Int走遍天下了?先看看WinAPI的参数类型声明,然后简单判断
一下是地址还是简单数值,凡是地址都用Ptr,简单数值有个特殊的SIZE_T类型是为了
输入可变类型的数值的,在Win32位系统为4字节,在Win64系统为8字节,所以我们记住
把它设为自适应的Ptr类型,其他的简单数值,除了Int64、Long Long这些明确的
8字节类型我们用AHK的Int64类型以外,其他的都用4字节的Int类型就基本上不会错了。
(偶尔有参数为浮点数类型,用AHK的8字节Double类型或4字节Float类型,很容易判断)

于是前面调用Dll文件中的InStr函数的例子写成下面的参数类型也不错:
Pos:=DllCall("Dll文件\InStr", "Ptr",&(s1:="abc123"), "Ptr",&(s2:="abc"), "Int")
为什么AHK要把地址类型(Ptr)分解成了Ptr类型、Str类型和*类型三种呢?

因为地址类型的参数同时具有输入输出功能。对于字符串和数值的输入输出情况:
对于输入,我们采用 Ptr,&a 传递AHK的变量地址给WinApi函数,还没多大问题
(字符串可能要StrPut转码,数值则必须用NumPut将字符串数值转二进制数值),
对于输出,由于AHK不能直接使用返回的二进制形式的字符串和数值,需要转换,
所以要用StrGet读取返回字符串,用NumGet读取二进制数值,这就比较麻烦。

Ptr类型只是死板地传递数值,没有多余动作,而Str类型和*类型都有神奇的
调用前后的内部转换操作,可以很方便地通过参数接收返回结果,所以很好用。

三、Str字符串类型的好处。

1、首先我们要认识到字符串在内存中是以什么形式保存的。
在AHK中我们用一对双引号包围的一些文字来表示字符串,比如"abc123"。
其实AHK在解析的时候还是会把它赋值给某个变量的,例如 a:="abc123"。
a变量代表一块内存,字符串保存到这块内存中变成了一个一个的数值,
称为字符编码。最初发明编程的人,由于仅使用英文26个字母和数字、
英文符号,加起来不到256个,所以规定了一套ASCII编码,每个字母、
数字等的编码都占1个字节。

后来要推广到所有国家,仅用英文字母肯定不够,所以各国都推出了
自己国家的编码,例如中国的汉字,1个汉字就用两个字节来编码。
通过各国编码时采用不同的码区,综合起来就形成了Ansi编码,
即英文的字母和数字、符号还是用1个字节的ASCII码,其他国家
的编码可能用一个或两个字节来编码。

由于编码越来越多,也比较乱,微软为了大一统,推出了一种Unicode
编码,即所有的字符都用两个字节来编码,并囊括了所有国家的文字。

目前Ansi编码和Unicode编码都很常用,因此WinAPI处理字符串时,
基本上同时提供两套API函数,分别以A和W结尾,对应两种编码。

AHK把除了对象以外的变量都保存为字符串,比如a:="123",a:=123,在内存中都保存为
字符串形式。怎么查看字符串的编码值呢?我们知道“&a”是获取a变量的内存首地址,
*是读取内存地址的1字节值的操作符,我们运行下面的代码看看效果:
-----------------------------
a:=12, p:=&a, n1:=*p, n2:=*(p+1), n3:=*(p+2), n4:=*(p+3), n5:=*(p+4), n6:=*(p+5)
MsgBox, %n1% %n2% %n3% %n4% %n5% %n6% ;-- 显示结果为:49 0 50 0 0 0
-----------------------------
这些数字代表什么含义呢?1的ASCII值为49,2的ASCII值为50,由于我的AHK是Unicode
版本的,Unicode版本的原生字符串(AHK中可用的)都是用两字节表示任何字符编码,
所以49 0占两个字节,50 0也占两个字节,最后两个字节0 0表示字符串的结尾\0字符。
如果AHK是ANSI版本的,原生字符串就是ANSI编码,英文和英文标点符号都占一个字节,
而汉字等语言的编码一个字占两个字节,字符串的结尾用一个字节0表示结束\0字符。

2、WinAPI函数读取字符串时要注意编码匹配。
前面说了,字符串参数压入栈中的是字符串的内存首地址,也就是"Ptr",&a这种形式。
但是假如WinAPI函数的参数需要ANSI编码的字符串,而AHK版本为Unicode编码怎么办?
使用原生编码显然错误,这时有两种方法,一种是手动转换编码,利用StrPut()把
Unicode的编码转换成ANSI编码保存到b变量的内存地址中,然后用"Ptr",&b传递参数。
另一种方法就是利用AHK提供的AStr参数类型,它会在调用前自动把参数的原生字符
串在临时变量的内存中转为ANSI编码并把临时变量的内存首地址压入栈中。
还有一个WStr参数类型,可以在调用前自动把ANSI编码的原生字符串转换为Unicode
编码,再把临时变量的内存首地址压入栈中。当然,如果原生变量与AStr/WStr指定
的一致,就不用转换,直接把a变量的内存首地址压入栈中,等效于"Ptr",&a 。

AHK采用了更聪明的方法确保原生编码符合WinAPI的需求,因为WinAPI为了适应两种
字符串编码,大多数函数都有A/W结尾的两个版本(如DeleteFileA、DeleteFileW),
AHK读取函数名称时如果找不到DeleteFile,会自动根据自身是ANSI编码还是Unicode
编码在函数名称后面加A或W,如果WinAPI准备了这两种版本的,就刚好智能匹配了。

由于AHK有这种智能匹配机制,所以一般用原生的Str类型(或Ptr,&a)就行了。
用Str的好处,一是可以直接采用字符串(比如"Str","abc123"),对于变量也不用
取地址&。另一个更重要的好处是,调用结束后,会更新对应变量的字符串长度。

3、Str类型可以更新变量的字符串长度,这对于通过变量地址返回字符串很好用。
由于AHK是自动管理内存的,变量占用的内存经常变动,需要增大内存时就要动态
申请内存然后把旧的内容拷贝过去,把变量的地址设到新的内存地址上,而字符串
的内存大小体现在字符串的长度上,所以AHK内部标记了每个字符串变量的长度。
AHK自身对字符串的改变操作,比如赋值、替换等都会自动调整这个长度标记。
而调用WinAPI中的函数,由于控制权不在AHK手中,发生了什么它也不知道,如果
原来的字符串为a:="abc123",但是如果WinAPI内部操作在末尾添加了"456\0"
(或者把a的内存内容改为了"xyz\0"),实际上a:="abc123456"(或者a:="xyz"),
而用b:=a,或者MsgBox, %a%来读取a的值时,AHK内部没有更新a的长度,还认为
字符串长度为6,就会造成错误。Str形式会更新字符串长度,而Ptr形式不会更新。

注1:如果不需要自动转换编码,那么Ptr,&a等效于Str,a。如果需要返回字符串,
Ptr,&a形式可以用VarSetCapacity(a,-1)或者StrGet(&a)两种方式手动更新长度。

注2:Astr和Wstr可能传入的是转换编码后的临时变量的地址,如果需要返回字符串,
WinAPI修改的也可能是临时地址中的内容,不能体现在参数的变量所在的内存地址中来,
所以如果需要返回字符串,就不能使用自动转换编码的Astr和Wstr,而要用Str或者
Ptr类型,因为这两种类型,压入栈中的地址就是变量的内存首地址。
如果WinAPI参数需要的编码不同于AHK的原生编码,需要手动用StrPut()转换成目标编码。
如果WinAPI返回的字符串编码与AHK原生编码不同,需要手动用StrGet()转换成原生编码。

四、*类型用于从参数获取函数返回数值。

1、WinAPI通过参数传入的内存地址可以返回多个数值,类似于ByRef类型。
WinAPI把某个数值保存到某个内存地址中并占几个字节(比如占1个字节对应
Char类型,4字节对应Int类型,8字节对应Int64类型),AHK不直接把参数
变量的内存地址通过"Ptr",&a传给WinAPI,而是通过Char*(CharP同义)
传递一个临时内存地址给WinAPI函数,函数把数值写入这个临时内存地址,
函数返回后,AHK自动从这个临时内存地址读取1个字节的数值到变量a,
这样就实现了通过传递临时地址的*参数来返回数值结果。虽然*类型一般用于
返回值,但如果这个Char*,a后面的a值也要作为输入值对WinAPI有用,AHK会在
临时内存地址中把a的值存入这个地址,注意char限定了仅写入1个字节的数值。

2、用Ptr代替*类型不可取。
如果用 "Ptr",&a 传递变量的内存地址给函数来接收返回数值可不可行呢?
首先考虑传递的地址中如果先需要一个输入值,这时要自己手动采用NumPut()写入
到地址&a中。假如我们设置a:=1,它不是已经是数值了吗,怎么还要NumPut()呢?
因为AHK内部把数值变量也都保存为字符串,所以a的内存首地址中保存的是1的
字符串编码,即 Asc("1")==>49,所以必须自己手动NumPut(1,a,"char")。
函数返回后,虽然WinAPI函数确实把返回数值写入到&a地址中了,但是我们要读取
出来的其实是字符串表示的数值,这才能用于AHK中,于是又要手动NumGet()读取。

五、利用Ptr类型输入数据结构。

Ptr类型只是简单传递了变量的内存地址给函数,它没有str、*类型那么
多的内部智能转换操作,它主要用于传递一个数据结构的地址给函数。
函数的参数往往需要特定的数据结构,因为只要得到这个结构的首地址,
按照这个结构的约定格式,就能用内存首地址加偏移获取各部分的数据了。

我们一般先用 VarSetCapacity(a,100) 申请一块内存,然后使用
NumPut() 按WinAPI约定的数据结构手动把数值写入a变量内存对应的
地址中,数据结构设定好后,再把&a地址传入函数。调用结束后,
还可以手动使用 NumGet() 从a变量的数据结构中读取需要的值。
NumPut()、NumGet()都是AHK对内存的指针操作,&取变量内存地址也是指针。

WinAPI的读写都是对内存地址的操作,所以在调用前一般要先用VarSetCapacity()
申请足够的内存,避免WinAPI乱写内存覆盖了有用的数据。

六、其他说明:

1、返回类型:WinAPI通过函数的返回值可以返回1个数值。通过寄存器EAX返回,
DllCall读取寄存器的数值到函数返回变量,这时由返回类型指定读取的字节数,
返回类型一般是地址型Ptr或者整型Int两种,比较特殊的是Str返回类型,
AHK会把返回的数值看做字符串的内存首地址,并复制字符串到返回变量中。

2、调用约定:C语言写的函数,返回类型前一般要添加"Cdecl",而WinAPI
使用标准调用形式则不用添加。若C函数编译时指定了使用标准调用也不用。

"C"调用约定是栈的平衡由调用者来完成,调用者压入了多个参数到栈中,
最后栈顶指针的恢复要由调用者来做。而标准调用则要函数自己来恢复,掉用者
只管压栈不管恢复。所以如果调用C函数不加上"Cdecl",默认使用标准调用,
栈的平衡无法完成,多次调用后会耗尽栈资源。

3、U前缀:指示使用无符号的类型,这对于输入数值没有意义(int64除外),
因为输入时指定char和uchar,写入内存的都是同样的数值,但对于WinAPI
的输出数值,即 函数返回类型 和 Char*类型 就有意义了,AHK内部读取的值
可能不同(类似于用NumGet()读取)。

4、Windows数据类型对应于AHK参数类型的简单判断。(懂了上面的就不难了)

简单数值的WinAPI参数绝大部分对应Int类型,比如:DWORD、LONG、BOOL、
COLORREF。带64的、LONGLONG对应Int64。浮点类型对应Float、Double。
比较特殊的是 SIZE_T 可变数据类型,它要用AHK自适应的Ptr类型来对应。
对于输入类型U前缀不重要,因此UInt写成Int也没关系。

内存地址的WinAPI参数对应于AHK的三种形式:一般WinAPI声明中的各种句柄
(H开头的)、带LP或P开头的、带PTR的都是指针,即地址类型,一般对应Ptr类型。
但是带STR的指针则对应Str类型更方便些(用Ptr可行但稍麻烦,参看上面的说明)。
如果是用于输出结果的指针就对应*类型(用Ptr就不可取,参看上面的说明)。

查找这个用户的全部帖子
表示感谢 引用并回复 移动视图置页面顶端
[+] 1用户表示感谢feiyue
2017-07-11, 02 : 04
RE: DllCall的使用简介
DllCall的使用简介已经更新,应该更容易懂了。
查找这个用户的全部帖子
表示感谢 引用并回复 移动视图置页面顶端
[+] 1用户表示感谢feiyue
2017-08-07, 09 : 21
RE: DllCall的使用简介
写得再多,不如一句话一个实例说明。
当然能编就已经不错了。

梦幻软件天堂
人生R棋随1出招借力打力多而合1扬C避D奇L断金
查找这个用户的全部帖子
表示感谢 引用并回复 移动视图置页面顶端
2017-12-16, 19 : 06 (这个帖子最后修改于: 2017-12-16 19 : 57 by feiyue.)
RE: DllCall的使用简介
下面我举个简单的例子吧,请对照前面讲的来理解。
GetUserName是一个WinApi函数,用于获得当前windows登录的用户名。
我们先看看MSDN网站的权威声明:
https://msdn.microsoft.com/en-us/library...s.85).aspx
----------------------------------
BOOL WINAPI GetUserName(
_Out_ LPTSTR lpBuffer,
_Inout_ LPDWORD lpnSize
);
DLL | Advapi32.dll
Unicode and ANSI names | GetUserNameW (Unicode) and GetUserNameA (ANSI)
----------------------------------
从上面的声明我们知道,这个函数位于Advapi32.dll库文件中,
它在库文件中有两个版本名称,分别是GetUserNameW处理Unicode字符串,
GetUserNameA处理ANSI字符串。

我前面说过,WinApi凡是牵涉到字符串的函数,大多都提供了A和W结尾的两个
版本的函数供用户使用,AHK可以根据自身是ANSI版还是Unicode版,在找不到
函数时会智能在函数名末尾添加A或W,从而智能找到刚好匹配的WinApi函数。
所以,AHK调用的第一部分为:DllCall("Advapi32.dll\GetUserName" 就好了。

当然因为我的AHK为Unicode版,我直接使用GetUserNameW也行,AHK能够
直接找到这个函数名,就不会在末尾添加A或W再尝试了。

如果直接使用GetUserNameA行不行呢?由于AHK能够直接找到这个函数名,
同样不会在末尾添加A或W再尝试了。这样该函数在处理输入输出时,内部默认
都是ANSI编码的字符串,所以对于输入字符串,我们需要将AHK原生的Unicode
编码字符串用StrPut转换成ANSI编码字符串,再把字符串首地址传给该函数,
对于输出字符串,需要用StrGet将返回的字符串首地址转换为Unicode字符串。

废话不多说,我们再看看它的参数。它有两个参数,我们一一分析。

第一个参数 “_Out_ LPTSTR lpBuffer”,用于返回Windows登录的用户名字符串。
这个参数是输出的,以LP开头的都是指针,也就是字符串首地址,毫无疑问,
我们用AHK的地址类型“Ptr”作为参数类型是可以的。我前面说过,对于含有STR
的指针,使用“Str”类型会更方便些,下面会详细分析。

第二个参数 “_Inout_ LPDWORD lpnSize”,用于设置处理的字符串长度。
这个参数既是输入一个长度最大值,又是输出结果的长度值,以LP开头的都是指针,
毫无疑问,我们用AHK的地址类型“Ptr”作为参数类型是可以的。但是我前面说过,
对于输出数值的地址参数,使用“*”类型可以自动转换,即输入时,将AHK常规的
字符串型数字,用NumPut写入一个临时内存地址,返回时,从临时内存地址用
NumGet读取数值转换为字符串型数字,供AHK常规使用。所以这个参数用
“Ptr”类型不妥,使用“Int*”则刚刚好。使用无符号的“UInt*”也可以,从临时内存
读取数值时,有符号的Int类型,因为32位二进制中第一位表示正负符号,只有31位
表示数值,所以最大范围为2147483647 (0x7FFFFFFF),而无符号的UInt类型
表示的数值最大范围为4294967295 (0xFFFFFFFF),范围翻了一倍。但是对于
我们这个函数要返回的登录用户名字符串长度而言,长度只有几十字节,所以
没必要用UInt。不过Windows的DWORD类型其实对应于无符号的“UInt”。

参数类型搞清楚了,就可以写出调用格式了:
DllCall("Advapi32.dll\GetUserName", "Str",name, "Int*",size)
最后用消息框显示结果:
MsgBox, % "登录用户名:" name "`n字符串长度:" size
这样就可以了吗?

不行。我前面说过,WinApi都是对内存地址的操作,所以在调用前一般要先用
VarSetCapacity()申请足够的内存,避免WinAPI乱写内存覆盖了有用的数据。
这个函数把返回用户名字符串写入name变量的内存地址,但是我们的AHK的变量
初始都为空的,内部合法占用的内存为0字节,明显不够WinApi写入的。为了可靠
地让WinApi操作合法的内存,我们用VarSetCapacity(name,100)手动申请100
字节的内存给name变量合法占用,因为100字节内存对于ANSI编码的英文可以
写入99个字母(加上末尾的“\0”字符),对于Unicode字符可以写入49个字符,
应该够用了。所以最后完整的调用代码为:
--------------------------------
size:=100, VarSetCapacity(name, size)
DllCall("Advapi32.dll\GetUserName", "Str",name, "Int*",size)
MsgBox, % "登录用户名:`t" name "`n`n字符串长度:`t" size
--------------------------------
这个函数其实有个返回类型“BOOL”,用于返回函数调用是否成功,对应于
AHK的Int类型,由于我们不需要该返回值,所以可以忽略返回类型参数那部分。

最后我们再假设WinApi只有GetUserNameA这一个函数,而我们的AHK又是
Unicode版的情况。对于输入字符串参数,比如 a:="abc",我们只要使用
"AStr",a 即可让AHK帮我们自动转换为临时变量的ANSI字符串传给WinApi。
但是这个函数需要从参数的地址返回字符串,所以不能使用“AStr”类型,需要
手动转换,可以这样做:
--------------------------------
a:="abc"
VarSetCapacity(b, StrLen(a)*2+100) ;-- 6字节不够用
StrPut(a, &b, "CP0")
--------------------------------
这样b变量的内存地址就有了ANSI编码的字符串内容,然后:
--------------------------------
DllCall("Advapi32.dll\GetUserNameA", "Ptr",&b, "Int*",100)
--------------------------------
把b变量的内存首地址传给WinApi,最后读取WinApi的返回结果时
还要把ANSI编码的结果转换回Unicode编码供AHK使用:
--------------------------------
name:=StrGet(&b,"CP0")
MsgBox, % "登录用户名:`t" name
--------------------------------
这样就大功告成了。虽然麻烦点,但是对于WinApi库函数中没有与
AHK本身Unicode还是ANSI版匹配的情况,只好这样手动解决。

由于例子比较简单,没有牵涉到WinApi参数常见的传入数据结构。
其实构造数据结构很简单,先用 VarSetCapacity(a,16) 申请内存,
再用 NumPut(写入数值, a, 偏移字节, "Int") 根据WinApi的数据结构
要求写入数值到正确的偏移位置,然后传入 "Ptr",&a 给WinApi即可。

查找这个用户的全部帖子
表示感谢 引用并回复 移动视图置页面顶端
2017-12-16, 20 : 47
RE: DllCall的使用简介
感谢feiyue大神的讲解!非常有用!
查找这个用户的全部帖子
表示感谢 引用并回复 移动视图置页面顶端
发表回复 


论坛跳转:


联系我们 | Autohotkey 中文站 | 回到顶部 | 回到正文区 | 精简(归档)模式 | RSS 聚合