aardio 文档

用 aardio 创建 ActiveX 控件

在 aardio 中可以创建 ActiveX EXE ,用于实现进程外 COM 控件。 可在其他支持 COM 控件的编程环境中嵌入入或调用 aardio 编写的 ActiveX 控件。

创建 ActiveX 工程

在 aardio 开发环境中点击『主菜单 / 新建工程』打开工程向导。 在工程向导中选择创建『窗口程序 / ActiveX EXE 』工程,并在向导界面输入 COM 控件类名 (也就是 ProgID )。例如我们输入 COM 控件类名 aardioTestControl.Sample 就会自动在工程中创建 /lib/aardioTestControl/Sample.aardio 这个类库。

创建 ActiveX 工程以后,我们只要简单地打开 /lib/aardioTestControl/Sample.aardio 添加我们需要导出的 COM 接口函数即可。

一般使用 ActiveX 工程向导自动生成的 COM 类型库( typelib ) 就可以了,不需要学习复杂的 ODL 语法。

将 ActiveX 工程发布为 EXE 文件以后,直接双击运行 ActiveX EXE 就可以注册、卸载 COM 控件。

ActiveX 工程源码解析 - main.aardio

创建 ActiveX 工程以后,我们在打开负责启动程序的主代码文件 /main.aardio,可以看到关键代码如下:

import com.activeX;

//导入 aardio 类库
import aardioTestControl.Sample;

//将 aardio 类库导出为 COM 接口,参数指定的 aardio 类名名称也就是 ProgID。
var activeX = com.activeX("aardioTestControl.Sample") 

//启动 ActiveX 控件。
var ok,message = activeX.main();

上面的代码依次执行了以下步骤的操作:

  1. 我们通过 import aardioTestControl.Sample 导入了 aardio 类库。

  2. 然后我们用 com.activeX("aardioTestControl.Sample") 将 aardioTestControl.Sample 这个类库的名称指定为 ProgID .

  3. 最后我们使用 activeX.main() 启动了 ActiveX 控件。

activeX.main() 的返回值又分为以下几种情况:

检查 activeX.main() 返回值的示例代码如下:

var ok,message = activeX.main();

if( !ok && !message  ) {  
    var id = win.msgbox("可用的启动参数如下:

/r 或 /RegServerPerUser 注册组件
/u 或 /UnregServerPerUser 卸载组件

按『是』注册控件,按『否』卸载控件","activeX控件演示",3/*_MB_YESNOCANCEL*/)   

    if( id == 6/*_IDYES*/ ){
        activeX.register(); 
        win.msgbox("注册组件成功");
    }
    elseif( id ==7/*_IDNO*/ ) {
        activeX.unRegister();
        win.msgbox("卸载组件成功");
    } 
} 
elseif( message ){
    //通过命令行参数注册或卸载控件回显信息
    if(!ok) win.msgbox(message); 
}
else {
    //ok为真,message为空表示组件成功启动
}

ActiveX 工程源码解析 - COM 类库

我们看一下 COM 类库 /lib/aardioTestControl/Sample.aardio 的主要源码:

namespace aardioTestControl;

class Sample{

    //构造函数,参数 activeX 为 com.activeX 实例对象。
    ctor(activeX){
        this.activeX = activeX;
    }


    //自定义需要导出的 COM 按口函数
    Add = function(a,b){
        return a + b
    }    

    //以下为 IDispatchExecutable 接口函数

    Quit = function(){ 

        // COM控件调用方必须调用这个函数退出进程
        this.activeX.stop(); 
    }

    Invoke = function(methodName,...){

        //以 ownerCall 方式获取函数对象,ownerCall 指效果如同 this.methodName() 格式调用函数,以保证函数内的 owner 参数指向 this 对象。
        var f = ..table.getCall(this,methodName);
        return invoke(f,this,...);
    }

    GetAttr = function(k){
        return ..table.getAttr(this,k);
    }

    SetAttr = function(k,v){
        return ..table.setAttr(this,k,v);
    }

    GetItem = function(k){
        return this.Item[k];
    }

    SetItem = function(k,v){
        this.Item[k] = v;
    }

    Item = { }

    //指定对象的元表
    @{
        /*
        _item 元方法可指向返回表的函数,也可以直接返回一个表。 
        _item 返回的表用于指定查询 COM 默认属性(DISPID_VALUE)的表对象,同时也是 COM 枚举表。
        */
        _item = lambda() owner.Item; 

    }
} 

aardio 中创建的 ActiveX 类库默认继承自 COM 接口 IDispatchExecutable,所以必须实现 IDispatchExecutable 规定的接口函数。

aardio 表对象、函数对象、cdata 对象在 COM 接口中都会自动转换为 IDispatch 对象。导入 COM 控件的外部程序可以直接调用 IDispatch 对象。

请参考: COM 类型

有两个小细节需要注意:

下面是一个在导出的 COM 接口函数中使用输出参数的示例:

namespace aardioTestControl;

class Sample{

    ctor(activeX){
        this.activeX = activeX;
    }

    TestOutParams = function(str,out1,out2){
        //输出参数必须也是输入参数。

        out1 = 1;
        out2 = 2;

        //输出参数必须增加到返回值列表。
        return out1,out2;
    }

    //省略其他代码
}

对应的 ODL 接口声明:

    [ uuid(1C8736BC-8C0C-4DB6-9FAD-1C6A0CDF1FA2) ]
    dispinterface  IDispatchSample{ 
        properties:
        methods:  
            [ id(10) ]
            void TestOutParams( [in] BSTR str,[in,out] VARIANT *out1,[in,out] VARIANT *out2);
    } 

要注意在 ODL 声明中所有自定义的 DISPID 不应大于 10000 (这是因为 aardio 自动生成的 DISPID 以 10000 为起始值递增)。

下面重点说一下 COM 默认属性与 COM 枚举器:

COM 默认调用的 DISPID 为: DISPID_VALUE 。 例如在 VB 或 VBA 中用 comObj("项目名") = 值 写默认属性,用 v = comObj("项目名") 读默认属性。

在 ActiveX 类库中创建窗口

如果要在 ActiveX 控件中创建窗口,则导出的 COM 类库至少要实现 CreateWindow,DestroyWindow,SetExtent 这三个接口函数。

import aardioTestControl.SampleForm;
namespace aardioTestControl;

class Sample{

    ctor(activeX){
        this.activeX = activeX;
    }

    /*
    创建窗口。
    hwndParent 参数为父窗口句柄。
    x,y 为控件坐标。
    cx,cy 为控件宽度与高度。
    */  
    CreateWindow = function(hwndParent, x, y, cx, cy){
        this.form = ..aardioTestControl.SampleForm(hwndParent);
        this.form.setPos(x, y, cx, cy);
        this.form.onDestroy = function(){
            this.activeX.stop();
        }

        this.form.external = this;
        this.form.externalEvent = this.externalEvent;
        return this.form.hwnd;
    }

    //销毁窗口事件
    DestroyWindow = function(){
        this.form.close();
        ..win.quitMessage();
    } 

    //自适应窗口大小
    SetExtent = function(cx,cy){
        this.form.resize(cx,cy) 
        return true;
    } 

    //省略其他代码
} 

创建窗口的 aardioTestControl.SampleForm 类库源码如下:

import win.ui;

namespace aardioTestControl;
class SampleForm{
    ctor(parent,tParam){
    /*DSG{{*/
    this = ..win.form(text="test.control";right=757;bottom=467;border="none";exmode="none";mode="child";parent=parent;tParam=tParam)
    this.add(
    button={cls="button";text="Button";left=71;top=60;right=283;bottom=150;z=1}
    )
    /*}}*/

    this.button.oncommand = function(id,event){
        /*
        所有用 com.activeX 的 COM 类库创建的对象都会增加事件对象 externalEvent 。
        通过 externalEvent 可以触发调用方指定的 COM 事件。

        注意 VB6 不支持后期绑定事件,需要在 odl 事件接口中明声明事件函数。
        */
        this.externalEvent.onHello("hello")
    }

    this.show()
    };
}

if( !owner ) ..aardioTestControl.SampleForm().doModal();

/**intellisense()
aardioTestControl.SampleForm() = 窗口类\n!winform.
end intellisense**/

ODL 类型声明

aardio 工程向导创建的 ActiveX 工程已提供了默认的 /typelib/typelib.odl,一般不需要修改。

如果要改动 ODL,请参考 请参考 ODL 语法指南

默认的 /typelib/typelib.odl 内容如下:

import "aardio.idl";

[
uuid(D0A0632B-C202-48FA-9C7B-32E808D233EB),
version(1.0)
]
library aardioTestControl {

    importlib("stdole32.tlb"); 


    [ uuid(A880101D-7B64-4F82-B568-FDE288A488A8) ]
    dispinterface  IDispatchExecutableWithEvent  { 

        properties:
        methods: 
            /*
            VB 不支持后期绑定事件接口,
            可将下面的 IDispatchExecutableEvent 换为 IDispatchExecutableWithEvent 。
            并在下面明确添加接口函数。

            注意下面的 id(DISPID) 不要小于等于 0 也不要大于 10000。
            */
            [ id(10) ]
            void onHello( [in] BSTR str );

    };

    [ uuid(822687D3-D4C8-4B02-8959-03957C3DE586) ]
    dispinterface IDispatchSampleSample { 
        properties:
        methods:  
            /*
            输出参数必须是声明为 [in,out] VARIANT * paramName,
            并在实现接口的 aardio 函数的返回值列表中增加对应的输出参数。 
            */
            [ id(10) ]
            void TestOutParams( [in] BSTR str,[in,out] VARIANT *out1,[in,out] VARIANT *out2);

            //dispinterface 接口不能继承,有需要可以到 /.build/aardio.idl 中复制 IDispatchExecutable 的定义。

    };

    [ uuid(257D8BF9-426A-424A-8FA8-7F9701E784A2),control ]
    coclass Sample {

        //默认接口
        [default] dispinterface IDispatchExecutable;

        //默认事件源接口
        [default,source] dispinterface IDispatchExecutableEvent;
    }; 
};

Markdown 格式