aardio - 回调函数的方法解析

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 定义,调用也不会导致程序出错。

4、回调函数模板:

        这里提供我封装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);

5、如果你觉得这个方法不错,是不是可以叫他 “光庆回调法” 。

哈哈 ^_^。

你可能感兴趣的:(aardio,aardio,回调,dll)