错误捕获、调试技术

 

一、在运行时输出信息

1、在浮动信息栏输出信息

win.messagePrint("这是一个测试字符串");

只能指定字符串(string)类型的参数。
如果在发布的时候选择不显示信息窗口,则所有win.messagePrint函数将被自动忽略。
如果输出信息的第一个字符为@,将会自动生成一个循环转动的字符标记。示例如下:

如果输出信息的第一个字符为@,将会自动生成一个循环转动的字符标记。示例如下:

for i=1,20  do  --循环10次
    win.messagePrint("@ i="..i);

    delay(200); --延时0.2秒
    if( not nStop() ) then break end;--如果按了全部停止热键就退出循环
end;

2、用对话框输出信息

win.messageBox("信息","标题");
win.messageBox(
"信息");

第一个参数指定要显示的信息,第二个参数指定标题,第二个参数可以省略
只能指定字符串(string)类型数值(number)类型的参数。

3、使用控制台窗口输出信息

打开控制台窗口:

调用函数 win.consoleOpen()

关闭控制台窗口:

调用函数 win.consoleClose();
点击“全部停止”关闭控制台窗口。
点击“全部停止热键” (默认为 Ctrl + Alt + L )

在控制台输出信息:

使用 print(...) 函数,支持任意多个任意类型的参数,print会自动调用tostring将参数转换为字符串。
使用 io.write(...)函数,支持任意多个字符串(string)数值(number)参数,不支持其他数据类型。

在控制台读取用户输入的信息:

n = io.read("*n"); 等待用户输入一行直到输入回车并然后返回数字值,不是数字返回nil值
l = io.read("*l"); 等待用户输入一行直到输入回车然后返回字符串

下面是一个简单的示例

--打开控制台窗口
win.consoleOpen()
 
n = 23;--声明一个数字变量
str = "字符串" --声明一个字符串变量
local a; --声明一个空的局部变量
local t = {}; --声明一个table类型的局部变量
 
print(n,str,a,t);--输出变量值

print("t的数据类型:",type(t)); --输出type函数的返回值

delay(1000);--延时一秒
win.consoleClose(); --关闭控制台

二、理解错误信息

1、理解LAS脚本程序错误信息

切换到“源码编辑”界面,点击工具栏第一个按钮“新建源码”,然后输入下面的代码:

local a = 2
local b = ;--很明显这里出错了,等号后面少了代码

按F5快捷键运行上面的代码,我们看到错误信息详细的标明了错误所在的位置、错误原因:

b = 后面应当写表达式才是一个完整的赋值语句,分号不应当直接出现在等号后面。
"LAS脚本块内部标识"是模拟精灵内部生成的用来标记一段脚本程序的。char:0x3B是错误所在字符的十六进制字节码。
分号的十六进制字节码就是0x3B。


2、理解fap模拟程序错误信息

fap模拟程序通常在"脚本区块"中编写脚本代码,"脚本区块"实际上就是一个匿名函数.

在ApeML源代码中,每个"脚本区块"的第一行实际上就是包括 " <![CDATA[ " 的这一行。

源码编辑器的行号 = “ <脚本区块....名称=出错区块的名字”所在行号 + 错误提示的行号


三、抛出错误

通俗的说就是强行制造一个错误.使用error函数抛出一个错误.

error (message [, level])
终止最后一个受保护的函数的运行,返回错误信息message,而该函数的出错信息不会返回.
参数level指定了出错信息所反映的位置,取默认值1时,指向调用出错的函数的位置,取2时,指向引起error的函数被调用的位置,依此类推。
有了这个参数就可以准确的指出到底是谁导致了错误。

level这个函数可能有点难以理解,如果你弄不明白可以省略( 省略则使用默认值 1 )。
下面我们用两个例子来解释一下level参数的用法。

function func(code) 
    if(code <0)then
   
    error("code参数不能小于0",1);
-- 当level参数为1,指明错误发生在当前函数, 错误行号指向这句代码
    end;
end;
 
   
func(-3);

function func(code) 
    if(code <0)then
   
    error("code参数不能小于0",2);
--当level参数为2,指明错误发生在调用这个函数的函数.
    end;
end;
 
   
func(-3);
-- 错误行号指向这里

有时候可能会遇到一些系统错误,例如执行win库的函数,读写文件,调用WinAPI等等操作时发生错误,
我们可以通过win.lastError()函数自动获取系统提供的借误代码及错误信息.
code,msg = win.lastError(); --第一个返回值为错误代码,第二个返回值为错误信息。
下面是一个示例。


str =string.load("c:\\no-file.txt");
if(not str )then
    local code,msg = win.lastError();
    win.messageBox(msg,"win.lastError返回的错误信息");
end;




四、断言函数

assert(条件,"出错信息") ;
assert首先计算两个参数的值,并检测第一个参数的结果,如果为false,则抛出错误(assert的第二个参数指定出错信息)。

请您试试运行 assert(false,"出错了") 这句脚本,您将看到弹出您指定的错误信息。
再试试运行 assert(true,"出错了") 这句脚本,因为断言成功,不再提示错误。

如果第一个参数不为false(或者其他不是nil的值),断言成功,会返回第一个参数,第二个参数的值。如果第一个参数是一个函数,则返回函数的返回值。

我们看这句代码 str = string.load("c:\\no-file.txt");
打开并读取 c:\xxxx.txt中的文本并返回给str变量。
现在我们希望用 assert断言string.load是否成功。您可能会想到这样写

assert(string.load("c:\\no-file.txt"),"打开c:\\no-file.txt时出错了");
str = string.load("c:\\no-file.txt"); --很糟糕的思路,string.load要执行两次

很糟糕的思路,string.load要执行两次。下面的写法更好一些
str = string.load("c:\\no-file.txt");
assert(str,"打开c:\\no-file.txt时出错了");--这样写好多了,string.load仅执行一次

这样写string.load仅执行一次,但是仍然不够简洁,下面我们利用assert返回函数的返回值。
str = assert( string.load("c:\\no-file.txt") , "打开文件时出错了" );


--您是否想自动取得string.load为什么会出错呢?使用code,msg = win.lastError() 即可。
--您可能会想到,是否可以将 win.lastError()作为assert的第二个参数自动取得系统提供的错误信息?
--但是我们遇到一个参题, 我们必须取win.lastError()的第二个返回值,这时候就要用到select函数了

str = assert( string.load("c:\\no-file.txt") ,select( 2, win.lastError() ) );
--win.lastError()的所有返回值作为参数传递给select 等于 select(2,code,msg)
--select从第二个参数向后数,找到第一个参数指定的参数并返回

有的函数第二个返回值会返回错误信息,这时候我们可以直接将函数的第二个返回值作为assert的第二个参数。
示例:

file = assert(io.open("c:\\no-file.txt", "r"));-->显示io.open的第二个返回值 No such file or directory

assert会返回多个值,但是我们可以用一个变量来接收返回值丢弃其他的返回值。
我们也可以用括号强制assert只能返回一个值。请运行下面的代码,注意他们的区别。

win.consoleOpen()

print(   assert(true,"出错了") ); -->显示两个返回值

print( ( assert(true,"出错了") ) );-->显示一个返回值


五、异常和错误处理

有时候,我们需要自已捕获运行时错误,而不是由模拟精灵来进行错误处理。

1、pcall函数

pcall (func, arg1, arg2, ...)
保护模式调用他的第一个参数func(函数对象)并运行,第二个参数以后的参数作为函数func的参数。

执行func时如果没有异常和错误,pcall返回true和函数func返回的任何值;否则返回nil加错误信息.

function f()
    error(
"错误信息");--error函数抛出错误。
end;

ok,
errmsg = pcall(f);--执行函数 f()
--失败了,所以ok等于false

win.consoleOpen();
print(ok, errmsg   );


错误信息不一定非要是一个字符串,传递给error的任何信息都会被pcall返回.请看下面的例子:

function f(str)
    local tab = {code=12,msg="错误信息"..str};--创建一个table字典     
    error(tab);--error函数可以抛出任何变量作为错误返回值。
end;

ok,tab = pcall(f,"我是谁");--执行函数 f("我是谁"
)
--失败了,所以ok等于false

win.consoleOpen();
print(ok, tab.code , tab.msg  );

虽然你可以使用任何类型的值作为错误信息,通常情况下,我们使用字符串来描述遇到的错误信息

2、xpcall函数

xpcall (func, err)
类似于pcall,但允许指定错误处理函数.

err是一个错误处理函数,xpcall首先调用func,如果遇到错误,将错误参数传递给err函数处理
然后xpcall返回false,然后返回err函数的所有返回值。

xpcall在栈释放以前调用错误处理函数err,所以在err函数中可以调用debug库中的函数收集错误相关的信息。
而pcall返回错误信息的时候他已经释放了保存错误发生情况的栈的信息。不能调用debug库收集错误相关的信息。


如果调用func没有发生错误,xpcall返回true,然后返回func函数的所有返回值。

function f()
    error("错误信息");--error函数抛出错误。
end;
 
function catch(msg)
    return "捕获到一个错误:"..msg;
end;
 
ok,errmsg = xpcall(f,catch);--执行函数 f()
 
win.consoleOpen();
print(ok, errmsg );