aardio中,不免要与各种dll等外部文件函数进行回调,经过不断测试,总结出几种比较稳定的回调方法,如下:
这是最直接的方法:aardio 将函数转变为 stdcall 方式,将 stdcall 函数指针传递给 dll ,由 dll 直接调用回调函数。
dll 传递 int、bool 等类型的参数,比较简单,直接声明和传递即可。
下面以传递 ansi编码文本( dll传递ansi编码,aardio解析为utf8编码 )为例,由简入繁的解析一下:
1、声明为字符串 string
// ansi文本传递方式1:直接声明为字符串 string
// 因为传递的是 ansi 字符,需要先转码为 utf8,aardio才能正确识别。
func= function(ansiString){
var utf8String = ..string.fromto(ansiString,0,65001)
return 1;
}
// 将 aardio 函数,转换为 stdcall 函数:
stdcall = ..raw.tostdcall(func,"int(string)",this)
// 将 stdcall 函数指针,传递给 dll 调用。
dll.setCallBack(stdcall);
2、 声明为指针 pointer
// ansi文本传递方式2:声明为指针 pointer
// 因为字符串本身就是传址,所以可以更暴力,直接传指针。
func= function(PansiString){
var utf8Para = ..string.fromto(..raw.str(PansiString))
return 1;
}
// 将 aardio 函数,转换为 stdcall 函数:
stdcall = ..raw.tostdcall(func,"int(int,pointer)",this)
// 将 stdcall 函数指针,传递给 dll 调用。
dll.setCallBack(stdcall);
3、 声明为整数 int
// ansi文本传递方式3:声明为 int
// 指针也是数值,可以当做int使用,但要这样还原成string:
func= function(intAnsiString){
var utf8Para = ..string.fromto(..raw.str(topointer(intAnsiString)))
return 1;
}
// 将 aardio 函数,转换为 stdcall 函数:
stdcall = ..raw.tostdcall(func,"int(int)",this)
// 将 stdcall 函数指针,传递给 dll 调用。
dll.setCallBack(stdcall);
1、stdcall 函数的存活期:
// 必须要保证 stdcall 是在 dll 调用期间永久有效的,否则会出现闪退等现象。
// 通常正确的方式是,将 stdcall 声明为一个不会销毁的变量,拥有正确的存活期。
// 如:
func= function(ansiPara){
return 1;
}
stdcall = ..raw.tostdcall(func,"int(string)")
dll.setCallBack(stdcall);
// 错误方法:
func= function(ansiPara){
return 1;
}
dll.setCallBack(..raw.tostdcall(func,"int(string)"));
// 上面的 stdcall 也就是..raw.tostdcall() 存活期就出现问题
// 因为他扮演了一个临时变量的角色,会随着 setCallBack 函数终止而被回收。
// 所以会因为 setCallBack 的存活期终止,导致 dll 调用出错。
2、回调函数返回值:
因为静态编程语言数据声明时已有默认数据,比如:
- int 类型默认值为 0
- bool 类型默认值为 false
- string 类型默认为 “”
当 aardio 回调函数无返回值时,dll 通常会有一个初始返回值。
所以,aardio 没有返回值的情况下,我一般都给 dll 返回 -1,以免让他误以为 aardio 传递的是 0 或 FALSE。
dll 给 aardio 发送 _WM_THREAD_CALLBACK 线程回调消息(或其他自定义消息),将函数名称及参数信息,通过消息参数进行传递。
aardio 接收 _WM_THREAD_CALLBACK 线程回调消息(或其他自定义消息)后,分析消息参数,获取函数名称和函数参数信息后,再由 aardio 负责调用回调函数:
此法被用于aardio 范例程序 \ 调用其他语言 \ Delphi \ 嵌入Delphi控件 例程。
官方 _WM_THREAD_CALLBACK 的使用请自行查看相关文档。
当然,我个人认为 aardio 自身解析 _WM_THREAD_CALLBACK 的过程太过于繁琐,使用起来也不直观,我更倾向于自己定义消息参数,Wparam 用于传递回调函数名称,如 “setText”,Lparam用于传递函数参数表,如:`{1,2,"hello world"}`,然后由aardio对消息参数进行简单的解析,基本满足需要了。
解析方法如下:
parent.wndproc = function(hwnd,message,wParam,lParam){
if message = 0xACCE/*_WM_THREAD_CALLBACK*/{
var func = ..raw.tostring(topointer(wParam)):""
var para = ..string.fromto(..raw.tostring(topointer(lParam)):"",0,65001)
if #func?parent[[func]]{
var rval = parent[func](..table.unpack(eval(para)));
if rval!==null return rval;
}
}
}
结合指针法的直接和消息法的简洁,我总结出一个比较简单、稳定、直接的回调方法。
在aardio中,只定义一个回调函数,该回调函数由 dll 调用,有两个字符串类型参数,第一个参数是“函数名称",第二个参数是“函数参数”:
var call = function(Pfuncname,Pfuncpara){
}
var stdcall = ..raw.tostdcall(this.call,"int(string,string)")
dll.setCallBack(stdcall);
dll 中需要调用某个aardio的函数时,只需要调用这个回调函数,比如:
stdcall("setCellText","{1,2,`这是第1行第2列的文本`}")
// 第一个参数"setCellText"是aardio函数名
// 第二个参数"{1,2,`这是第1行第2列`}"是传递给setCellText()的3个参数,以表的形式给回调函数。
回调函数收到dll的调用时,这样处理:
call = function(funcname,funcpara){
var func = ..string.fromto(funcname,0,65001);
var para = ..string.fromto(funcpara,0,65001);
if this[[func]]{
return this[func](..table.unpack(eval(para)));
}
}
由回调函数 call 解析 dll 想调用的函数名和函数参数,如果 aardio 定义了该函数,则由 call 负责调用该函数,并将参数传递过去,同时将该函数的返回值返回给 dll。
而 aardio 中 setCellText 函数只需要这样定义:
setCellText = function(row, col, text){
...
return true;
}
// 因为 call 调用此函数时,是使用索引操作符[]调用的,所以此函数的 owner 为空。
// 所以此函数内不能使用 owner。
这样,setCellText 函数就间接被 dll 回调了。
这样做的好处如下:
1、只需要在 aardio 中定义 一个 回调函数,并将其转为 stdcall 即可。
2、其它的函数在 aardio 中当做一般函数定义即可,而不用关心转成 stdcall 的问题。
3、在 dll 中只需用 stdcall 间接调用其它函数即可,而不用去逐个声明。调用方法也很简单,只需提供函数名称和参数表即可。即使这个函数没被 aardio 定义,调用也不会导致程序出错。
这里提供我封装dll类时,定义的回调函数,仅作参考:
this.call = function(funcname,funcpara){
var func = ..string.fromto(funcname,0,65001)
var r = null;
if this[[func]]{ // 如果定义了该函数则调用
var para = ..string.fromto(funcpara,0,65001)
if para="{}" { //无参调用该函数
r = this[func]()
} else { //有参调用该函数
r = this[func](..table.unpack(eval(para)))
}
}
return r===null?-1:r;
}
this.stdcall = ..raw.tostdcall(this.call,"int(string,string)")
this.dll.setProp(this.tag,"callback",this.stdcall);