Qt 之 ActiveX要点记录

一 参考文档

QT官方文档

二 QT的ActiveX说明

  1. Qt的 ActiveX 和COM接口技术是针对windows 做的接口封装,而且本身ActiveX 就是微软提出来的易于不同开发语言调用的接口,所以编译器选择MSVC。
  2. QT的这一封装的目的有两:让Qter能在写应用时能顺利的引入ActiveX和COM控件为已用; 让Qter写的应用程序可以作为COM 服务进程或者将任意个QObject和QWidget制作COM对象和Activex控件使用。
  3. QT的ActiveX框架由QAxContainerQAxServer两个模块组成,前者实现了2中的第一个目的,后者实现第二个目的。

    两个模块都是以静态库的形式提供的,如果QT库里没有,你无法使用,要先编译出来静态库才能使用。

    模块提供的类作用pro需要引入的库
    QAxContainerQAxObjectCOM组件的容器,用来装载COM组件,将com中的属性,方法,事件转化成QObject对象中的属性,槽函数和信号QT += axcontainer
    QAxWidgetActiveX控件的容器,其它同上同上
    • QAxObject,QAxWidget都是继承自QAxbase,QAxbase是个抽象类,提供了一系列API用来初始化和对接com,ActiveX 组件。
    • 继承自QAxObject,QAxWidget的类都不能再使用Q_OBJECT宏了,所以在它们的子类中除了控件转化后以有的,无法再自行增加槽和信号事件,如果想增加可以将其做为另一个QObject的成员来,在QObject中添加。
    模块提供的类作用pro需要引入的库
    QAxServerQAxAggregated用于实现更多COM服务接口的抽象基类,详见More InterfacesQT += 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文件中:
  1. 程序可执行文件(估计是入口代码部分)将连接到qaxserver.lib而不再是qtmain.lib,一般的应用程序都是连接到qtmain.lib中。
  2. 在编译成功插件后,调用idc(The Interface Description Compiler )程序生成一份COM插件对应的IDL文件。没有指定版本默认是1.0

    idc.exe release\xxx.exe /idl release/xxx.idl -version 1.0
  3. 利用MIDL工具将IDL文件编译进类型库文件中。

    midl release/xxx.idl /nologo /tlb release/xxx.tlb
  4. 再次使用idc将编译出来的类型库文件做为二进制资源文件附加到COM插件中去。
  5. 注册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可以用uuidgenguidgen生成,具有唯一性。还有一些其它类属性可供添加,详见 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;
    }
    

注意事项

  1. 并不是说按上述创建出来的类里的所有方法都能暴露出来做为COM服务提供给客户端。因为QT只会将特定的三类转化为COM相对应的接口,你可以看相应的IDL文件,查看转化结果做验证,比如上面的function2就没有,没有那你就无法调用。

    创建的QT COM类COM对象idl文件中的位置
    类属性属性 propertiesproperties:下
    类的公有槽函数方法 methodmethods:下
    类信号事件 eventmethods:下

    说下信号:如上例中的信号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();
    }
  2. QT的属性类型和方法中的参数类型都必须在QT所支持的列表里才能正常转化成对应的COM数据类型,否则QT ActiveX框架会自动忽略相应的方法属性的转化,就没法导出使用了。
    支持的数据类型详见官网

  3. 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服务

  1. 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
  2. exe形式的可以不需要注册,在启动时加上-activex参数告诉它以提供COM服务的形式启动(默认是以可执行程序启动),那客户端就可以正常使用它提供的服务了。它也可以注册到系统中去,这样不用每次都加-activex启动。

  3. 打包成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不熟所以记下,省的下次又忘了。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值