一 参考文档
二 QT的ActiveX说明
- Qt的 ActiveX 和COM接口技术是针对windows 做的接口封装,而且本身ActiveX 就是微软提出来的易于不同开发语言调用的接口,所以编译器选择MSVC。
- QT的这一封装的目的有两:让Qter能在写应用时能顺利的引入ActiveX和COM控件为已用; 让Qter写的应用程序可以作为COM 服务进程或者将任意个QObject和QWidget制作COM对象和Activex控件使用。
QT的ActiveX框架由QAxContainer和QAxServer两个模块组成,前者实现了2中的第一个目的,后者实现第二个目的。
两个模块都是以静态库的形式提供的,如果QT库里没有,你无法使用,要先编译出来静态库才能使用。
模块 提供的类 作用 pro需要引入的库 QAxContainer QAxObject COM组件的容器,用来装载COM组件,将com中的属性,方法,事件转化成QObject对象中的属性,槽函数和信号 QT += axcontainer QAxWidget ActiveX控件的容器,其它同上 同上 - QAxObject,QAxWidget都是继承自QAxbase,QAxbase是个抽象类,提供了一系列API用来初始化和对接com,ActiveX 组件。
- 继承自QAxObject,QAxWidget的类都不能再使用Q_OBJECT宏了,所以在它们的子类中除了控件转化后以有的,无法再自行增加槽和信号事件,如果想增加可以将其做为另一个QObject的成员来,在QObject中添加。
模块 提供的类 作用 pro需要引入的库 QAxServer QAxAggregated 用于实现更多COM服务接口的抽象基类,详见More Interfaces QT += axserver QAxBindable 提供客户端到 QWidget的接口,提供了一些方法来让控件通知客户端它的某个属性变化情况,但该组件必须先继承自QWidget 同上 QAxFactory 用于创建COM服务的工厂类,用来提供组件的注册,导出组件的信息等 同上
三 QT的ActiveX的创建
step 1 修改工程
工程文件中添加QT模块axserver
可独立运行的exe形式
TEMPLATE = app QT += axserver
插件dll形式
TEMPLATE = lib QT += axserver CONFIG += dll
另外还可以添加资源,定义插件版本等信息如下,这里的版本在注册到系统注册表中时会用到。
DEF_FILE = qaxserver.def RC_FILE = qaxserver.rc VERSION = 2.5
-
对细节不想了解的跳过下面,在工程中加入axserver 模块后,qmake将会自行添加几个必须的构建步骤到makefile文件中:
-
- 程序可执行文件(估计是入口代码部分)将连接到qaxserver.lib而不再是qtmain.lib,一般的应用程序都是连接到qtmain.lib中。
在编译成功插件后,调用idc(The Interface Description Compiler )程序生成一份COM插件对应的IDL文件。没有指定版本默认是1.0
idc.exe release\xxx.exe /idl release/xxx.idl -version 1.0
利用MIDL工具将IDL文件编译进类型库文件中。
midl release/xxx.idl /nologo /tlb release/xxx.tlb
- 再次使用idc将编译出来的类型库文件做为二进制资源文件附加到COM插件中去。
注册COM插件服务到系统中去,成功后提示如下。
Server registered successfully!
step 2 编写用于导出的COM类
如何将普通的类转化为COM类呢?
要想实现QT的COM或ActiveX,你创建的每个想导出成COM的类必须都继承自QObject或QWidget或者它们的子类。继承自QObject及子类的为COM插件,继承自QWidget及子类的为ActiveX控件。
使用宏Q_CLASSINFO() 来为你每个要导出的COM类指定类ID,接口ID,事件ID(前两个是必须的,事件ID当有信号事件才需要否则不必),这些ID可以用uuidgen或guidgen生成,具有唯一性。还有一些其它类属性可供添加,详见 Class Information and Tuning
如果是想创建ActiveX类的话,还可以使用 Q_PROPERTY() 声明下属性的一些对应事项,如下,type ,name 和read function是必须项,还有些其它可选项。当然如果想要绑定控件的属性变化与客户端联系起来必须要用到 QAxBindable类中的
requestPropertyChange()
和propertyChanged()
两个方法。Q_PROPERTY(type name READ getFunction [WRITE setFunction])
其它的写法与平常一样写就好,这样这个类就可以做为com导出了,导出写法第三步。
示例如下:
#include <QWidget> class MyActiveX : public QWidget { Q_OBJECT Q_CLASSINFO("ClassID", "{1D9928BD-4453-4bdd-903D-E525ED17FDE5}") Q_CLASSINFO("InterfaceID", "{99F6860E-2C5A-42ec-87F2-43396F4BE389}") Q_CLASSINFO("EventsID", "{0A3E9F27-E4F1-45bb-9E47-63099BCCD0E3}") enum weekday{sun,mon,tue,wed,thu,fri,sat}; Q_ENUM(weekday) public: explicit MyActiveX (QObject *parent = 0); ~MyActiveX (); signals: void SIGxxx(QString result); public slots: bool function0(const QString &file); void function1(void); protected slots: void function2(QString &s); private: QString str; QImage image; }
注意事项
并不是说按上述创建出来的类里的所有方法都能暴露出来做为COM服务提供给客户端。因为QT只会将特定的三类转化为COM相对应的接口,你可以看相应的IDL文件,查看转化结果做验证,比如上面的function2就没有,没有那你就无法调用。
创建的QT COM类 COM对象 idl文件中的位置 类属性 属性 properties properties:下 类的公有槽函数 方法 method methods:下 类信号 事件 event methods:下 说下信号:如上例中的信号
void SIGxxx(QString result)
,转化后在客户端来看它就是一个“虚方法”(我是这么理解 的)且形式是一样的void SIGxxx([in] BSTR result)
,你只需要重写实现下就行,当控件发射该信号时,就会触发客户端进入到该方法中处理。
比如JS代码如下<object ID="myActive" CLASSID="CLSID:37EBD64C-4A0F-4A44-9643-C85172EA7237" > </object> function myActive::SIGxxx(result) { document.getElementById ('result').innerText = result; myActive.funtion1(); }
QT的属性类型和方法中的参数类型都必须在QT所支持的列表里才能正常转化成对应的COM数据类型,否则QT ActiveX框架会自动忽略相应的方法属性的转化,就没法导出使用了。
支持的数据类型详见官网- QT ActiveX也会导出Q_ENUMS()/Q_ENUM() 和Q_FLAGS()/Q_FLAG()修饰的枚举值。该枚举值必须在导出类中被定义,不能导出其它类的枚举值。
step 3 创建QAxFactory工厂,导出COM类
上述步骤只是创建了可以导出的COM类而矣,但要想让客户端能申请创建这个类对象,那这个类就必须让外部知道才能创建。而要想实现这个则必须要导出一个QAxFactory的实例出来,。该实例提供了向系统注册/销COM服务的方法,告知 COM系统,其导出了哪些类和类型可供它创建。
最简单的实现方法就是在main.cpp中使用QAxFactory提供的宏构造下就OK了,如下,其它方法见QAxFactory类。
QAXFACTORY_BEGIN("{ad90301a-849e-4e8b-9a91-0a6dc5f6461f}",// type library ID "{a8f21901-7ff7-4f6a-b939-789620c03d83}")// application ID QAXCLASS(MyWidget) QAXCLASS(MyWidget2) QAXTYPE(MySubType) QAXFACTORY_END()
说明下:上面导出了两个类对象MyWidget和MyWidget2,这样在客户端就可以通过这两个类的ID创建这两个类的对象,然后用它们导出的方法属性了。同时也导出了一个数据类型MySubType,供这两个类对象使用。
上面的宏看定义可知主要就是实现了一个QAxFactoryList 类,然后创建它返回一个QAxFactory 指针,关于什么时候调用的qax_instantiate方法还要研究了。QT中有个全局qAxFactory()
函数来获取这个实例。class QAxFactoryList : public QAxFactory { .... }; QAxFactory *qax_instantiate() { QAxFactoryList *impl = new QAxFactoryList(); return impl; }
对于out-of-process可执行程序来说,你还可以自己实现一个main函数来实例化一个 QApplication,然后像普通QT应用一样进入到循环事件中去,如下,然后用
QAxFactory::isServer()
函数来判断这个执行程序是在提供COM服务(执行是加个-activex参数),还是就是一个单独的执行程序(默认执行),如果是后者,则写代码实现你自己要想的执行效果。#include <QApplication> #include <QAxFactory> int main(int argc, char *argv[]) { QApplication app(argc, argv); if (!QAxFactory::isServer()) { MainWindow w; w.show();// 如果不提供COM服务那就做独立程序该做的事 return app.exec();//离开了{}这个作用域你的w就没了,所以可能看到的是一闪而过的画面,故加此。 } return app.exec(); }
注意事项:
QT ActiveX的COM服务必须有且仅有一个QApplication 实例跑起来,这样信号,槽函数才能正常驱动起来,但main函数并不是必须要实现的,因为 ActiveQt 提供了一个默认的mian函数,在这个默认的mian函数中,调用了 QAxFactory::startServer()并且创建了一个QApplication 实例然后调用exec()进入事件循环。所以上面的第一点看起来没有main函数,却一样可以提供com服务就是这个原因。
step 4 编译
没什么可说的
四 使用
step 1 保证运行条件
QT ActiveX和一般的共享库DLL一样,要想成功运行必须有完整的依赖,可以运行windeployqt.exe xxx.dll
来自动寻找拷贝。
另注意:要把 xxx.idl
,xxx.tlb
与exe放一起,不然执行是可以的,但插件服务却不正常了。
step 2 向系统注册安装COM服务
dll形式的必须先注册后使用。(如果是打包成CAB,客户端其实会先检验你系统有没有注册,没有则依据你的包里的inf自动注册),如果系统没有提供regsvr32注册程序,你可以自己实现
HMODULE dll = LoadLibrary("myserver.dll"); typedef HRESULT(__stdcall *DllRegisterServerProc)(); DllRegisterServerProc DllRegisterServer = (DllRegisterServerProc)GetProcAddress(dll, "DllRegisterServer"); HRESULT res = E_FAIL; if (DllRegisterServer) res = DllRegisterServer(); if (res != S_OK) // error handling
exe形式的可以不需要注册,在启动时加上
-activex
参数告诉它以提供COM服务的形式启动(默认是以可执行程序启动),那客户端就可以正常使用它提供的服务了。它也可以注册到系统中去,这样不用每次都加-activex
启动。- 打包成CAB包后发布。省去手动注册的麻烦。
step 3 支持的ActiveX Clients
- Internet Explorer(不支持firefox,chrome之类的)
- Microsoft ActiveX Control Test Container
- Microsoft Visual Studio 6.0
- Microsoft Visual Studio.NET/2003
- Microsoft Visual Basic 6.0
- MFC- and ATL-based containers
- Sybase PowerBuilder
- ActiveQt based containers
- Microsoft Office applications
step 4 具体使用
以IE为例,嵌入到网页端使用HTML的<object>
创建对象,使用<param>
初始化控件的属性 如下
<object ID="MyActiveX1" CLASSID="CLSID:ad90301a-849e-4e8b-9a91-0a6dc5f6461f">
<param name="name" value="value">
<\object>
<SCRIPT LANGUAGE="JavaScript" >
function initDev( form )
{
form.camera.value = MyActiveX1.init();
}
function do1()
{
MyActiveX1.function0();
}
function do0(form)
{
MyActiveX1.function0(form.saveload.value);
}
<!-- 与QT信号事件的关联 -->
function MyActiveX1::SIGxx(p_result)
{
document.getElementById ('result').innerText = p_result;
}
</SCRIPT>
<form>
Control button:<br />
<input type="button" value="init dev" onClick="initDev(this.form)" /> <br />
<input type="button" value="do some thing" onClick="do1()" /> <br />
<input type="button" value="do some thing else" onClick="do0(this.form)" />
save path:<input type="edit" name="saveload" value="0" /><br />
<label>结果:<input type="text" ID="result"></label>
</form>
上面就是依据类ID创建出这个类的实例MyActiveX1,然后初始化该对象的name值,调用它的方法。如果把<object>...<\object>
这段放到function MyActiveX1::SIGxx(p_result)
后会提示对象MyActiveX1没有定义,javascript不熟所以记下,省的下次又忘了。