在 aardio 中可以创建 ActiveX EXE ,用于实现进程外 COM 控件。 可在其他支持 COM 控件的编程环境中嵌入入或调用 aardio 编写的 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
,可以看到关键代码如下:
import com.activeX;
//导入 aardio 类库
import aardioTestControl.Sample;
//将 aardio 类库导出为 COM 接口,参数指定的 aardio 类名名称也就是 ProgID。
var activeX = com.activeX("aardioTestControl.Sample")
//启动 ActiveX 控件。
var ok,message = activeX.main();
上面的代码依次执行了以下步骤的操作:
我们通过 import aardioTestControl.Sample
导入了 aardio 类库。
然后我们用 com.activeX("aardioTestControl.Sample") 将 aardioTestControl.Sample 这个类库的名称指定为 ProgID .
最后我们使用 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为空表示组件成功启动
}
我们看一下 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 控件中创建窗口,则导出的 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**/
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;
};
};