在模拟精灵中使用标准DLL动态链接库

模拟精灵支持标准的DLL动态链接库,您可以使用其他编程语言轻松扩展模拟精灵的功能,也可以在模拟精灵中调用其他标准DLL提供的接口函数,如调用系统提供的WinAPI函数。

下面以调用user32.dll中的MessageBoxA函数举例说明:

messageBox = dll.open("user32.dll", "MessageBoxA", "V(I,S,S,I)","stdcall")
messageBox( 0, "对话框标题", "这是一个测试对话框", 0x00001000 )
dll.close(messageBox)

1、首先,使用dll.open函数导入函数并赋值给messageBox变量。
2、然后就可以象使用普通函数一样使用messageBox函数了。
3、当确认不再使用messageBox函数时,使用dll.close释放函数。(可以省略这句代码,交由模拟精灵自动释放)

一、引用外部DLL函数

dll.open函数说明:

在模拟精灵中使用的函数名字 = dll.open("dll文件名或路径","api函数名字","声明函数原形", "调用约定,可省略")

第一个参数指定dll路径,也可以仅指定文件名或相对路径(可以省略.dll后缀),
如果指定文件名或相对路径,模拟精灵首先在默认的插件目录Fairy_Ape\import下搜寻dll文件。
如果没有找到,将按以下顺序搜索:

1、模拟精灵所以目录。
2、当前目录。
3、Windows 系统目录。win.getSysDir() 函数检索此目录的路径。
4、Windows 目录。win.getWinDir() 函数检索此目录的路径。
5、PATH 环境变量中列出的目录。


最后一个参数指定调用约定,这个参数是可以省略的,默认的调用方式为stdcall.
在使用WinAPI时默认的调用约定也是stdcall,所以在调用WinAPI时最后一个参数也可以省略。
如果您在导入其他一些DLL中的函数时出现错误,可以尝试指定cdecl调用通常能解决问题。

最后我们说一下如何声明函数原形,这是最复杂也是最重要的一部份。
好在本手册中已经有了相关WinAPI函数的声明,直接复制即可使用,但是了解一下仍然是有用的。

首先,在模拟精灵LAScript中,所有数字都是number类型,因为模拟精灵是高级动态语言,对于类型是自动适应自动转换的。
但是在DLL中的类型相对就复杂的多,这就需要我们指定API函数中的参数类型及字节长度,这样模拟精灵才能自动的进行转换。
这个指定函数的参数、返回值的变量类型及字节长度的过程就称为声明函数原形

例如上面messageBox 的函数原形为V(I,S,S,I),参数及返回值的类型都用一个类型标记来指定。
括号内部表示参数,括号前面用一个字符标记返回值的类型。

函数原型指定参数类型(这里的类型与LAScript中的类型略有不同)与返回值,使用以下定义
s: a string in ANSI format. 字符串
S: 同上
c: Signed char value (8位) 字符值
C: Unsigned char value (8位) 无符号字符值
n: Signed short value (16位) 短整数
N: Unsigned short value (16位) 无符号短整数
i: Signed integer value (32位) 整数
I: Unsigned integer value (32位) 无符号整数
l: Signed long value (64位) 长整数
L: Unsigned long value (64位) 无符号长整数
f: Single-precision float in the native format (32位) 浮点数
F: 同上
d: Double-precision float in the native format (64位) 双精度浮点数
D: 同上
p: void *  Pointer(传递一个地址指针(数字值))
P: 变量按引用传址(如果参数有输出值,可以用这个标记)
v: void 表示无返回值(不能用于参数类型)
V: 同上

表达式:返回值(参数1,参数2,参数3......)
例 V(I,S,S,I) 括号前面的V表示返回类型为void(无返回值),括号内表示参数的类型


注意在API中,逻辑值通常有0为假_FALSE,非零为真_TRUE,而句柄通常以0为空值_NULL
而在LAScript,只要是存在的数据包括0都为真true,只在没有定义的空对象才认为是空值nil 空值等于罗辑值中的假false
请注意大写的_FALSE _TRUE _NULL专用于API函数以及部份win库的函数。

[P标记:有输出值的参数]

注意P是一个特殊的标记,如果一个参数类型被指定为P标记,那么实际参数如果为整数(如果小数部份为零同样被认为是整数)则被自动转换为4位整型并取得相应的指针值作为参数,如果为小数转换为8位浮点数并取得相应的指针值作为参数,如果为字符串则取得相应的指针值作为参数,如果为其他类型一律转换为NULL值。
然后在函数返回时自动将P参数添加到返回值中(第一个返回值是函数返回值,其他的返回值按P出现的顺序返回)

为了理解P标题,我们看一个例子:

messageBox = dll.open("user32.dll", "MessageBoxA", "V(I,P,S,I)","stdcall")

_,str = messageBox( 0, "对话框标题", "这是一个测试对话框", 0x00001000 )


无疑的,对话框上显示了错误的乱码,因为第二个参数传递的是一个指针,(没必要不要做类似的尝试,调用API最好清楚的指明正确的类型)

然后我们看看返回值,第一个 _ 表示 默认返回值void,当然你指定一个变量名也可以,_的意思与C++中的void是一样的。

第二个返回值则返回了第一个P表示的数据,猜猜下面这句代码会显示什么呢?
 
win.messageBox("返回值:"..str,"")

我们再来看一个完整示例 --用Win API检测是否上网和上网类型(通过局域网、广域网)


_NETWORK_ALIVE_LAN  = 0x00000001 --是否有可用网卡
_NETWORK_ALIVE_WAN  = 0x00000002 -- 是否有可用远程连接
_NETWORK_ALIVE_AOL  = 0x00000004 -- NETWORK_ALIVE_AOL(仅对98\95有效判断是否联上美国的网络)

--IsNetworkAlive的参数是传址的,所以需要用P来标记参数类型,然后在返回值中添加一个返回值flag
IsNetworkAlive = dll.open("sensapi.dll", "IsNetworkAlive", "I(P)","stdcall")
ok,flag = IsNetworkAlive(_NETWORK_ALIVE_WAN); --ok对应上面的默认返回值I,flag对应上面的输出参数返回值P
-- 注意上面的参数 ,应在网络连接以后延迟一下再检测是否有活动的连接
 
win.consoleOpen();
print("ok="..ok);

if( ok == 0 )then
     print("没有联网")
else
     if( ( flag & _NETWORK_ALIVE_LAN ) == _NETWORK_ALIVE_LAN) then 
          print("当前通过网卡连接网络")
     elseif( (flag & NETWORK_ALIVE_WAN) == _NETWORK_ALIVE_WAN)then
          print("检测到远程连接");
     else
          print("没有联网");
     end;
end;

delay( _CMD_LOOP )

dll.open 函数调用其他语言编写的DLL动态链接库时,参数支持自定义类型(struct)
以面以GetCursorPos举例说明,注意下面的代码稍有点复杂,仅作为声明struct的一个例子,实际上用 x,y = mouse.getPos() 即可方便的取得鼠标当前位置.

win.consoleOpen()

--GetCursorPos 的参数是一个struct POINT自定义类型的按引用传递方式,相应的参数标记为P(即传递类型指针)
GetCursorPos = dll.open("user32.dll", "GetCursorPos", "n(P)")
 
--[[在调用动态链接库时如何声明struct自定义类型呢?
例如,在GetCursorPos中需要用到的自定义类型POINT,在C语言中如下声明
struct POINT
{
    LONG  x;
    LONG  y;
}
--]]

--那么我们在LAScript中可以如下声明 struct POINT
point ={
x=0;
y=0;
};

--[[
因为table字典是无序的,而struct是有序的,我们还要在table中添加数组声明元素的顺序
并且在每个键名前附加一个字符标记相应元素的类型,

这些标记基本与dll.open的参数类型标记相同。
唯一的区别是声明struct时小写的s表示char[]数组,而dll.open中小写的s与大写的S都表示字符串指针。
--]]
point[1]= "ix"; --i表示x是一个32位整数(long)
point[2]= "iy";--i表示x是一个32位整数(long)

--上面的代码也可以写在一起
point={
x=0;y=0; --定义结构
"ix";"iy"; --定义结构中元素的类型、顺序
};

re,pt = GetCursorPos(point);

--point的值是按引用传递的,所以返回值pt等于参数point ,所以这里的pt返回值是可以不要的
print("GetCursorPos取得的坐标",point.x,point.y);


三、通过函数序号导入外部DLL函数

如果dll.open的第二个参数是字符串则通过函数名导入。
如果dll.open的第二个参数是数字则通过函数序号导入。

在模拟精灵中使用的函数名字 = dll.open("dll文件名或路径",api函数序号,"声明函数原形", "调用约定,可省略")

--[[
下面是一个例子
Shell32.dll有一个未公开的API函数,可以弹出关机对话框
但没有名字,索引号为60
--]]

SHShutDownDialog = dll.open("shell32.dll", 60, "i(i)")

SHShutDownDialog (0)

四、释放外部DLL函数

dll.close(外部DLL函数)
立即释放外部DLL函数。并不会删除函数变量量(函数变量指向无效的外部DLL函数)。

外部DLL函数 = nil;
立即删除函数变量,等待垃圾收集释放函数变量引用的外部DLL函数。

五、加载dll规则

1、dll.open函数对于同一个dll函数使用一次性加载。

即重复调用dll.open加载同一个dll中的同一个函数时直接返回已加载的函数.
而不会重复的从dll中导入相同的函数。

2、dll.open函数自动搜索dll文件的规则。

如果指定相对路径,将按以下顺序搜索dll:
1、小精灵插件目录、
2、模拟精灵插件目录,
3、按DLL常规搜索当前目录、系统目录。

六、与import的区别

注意 import()函数支持的是符合LAScript插件规范的标准化插件,而dll.open()支持的是普通dll文件,
通过comx插件可支持ActiveX、.NET组件,至v4.55版本,模拟精灵已经支持几乎所有格式的插件.