1. 程式人生 > >Qt中使用ActiveX(一)

Qt中使用ActiveX(一)

由於最近需要使用ActiveX,一般來說可以使用微軟提供的MFC或者ATL框架來開發,由於我個人對這部分內容不是很熟悉,好在Qt也提供對於ActiveX的支援。本文主要記錄個人學習ActiveX的一些內容,方便日後查閱。本文以Qt5(5.3.1)提供的ActiveX為參考,但是由於ActiveX這部分比較穩定,因此Qt4應該也是一樣的。

  • 概述
Qt提供了QtActiveX模組來支援微軟ActiveX的開發,Qt的ActiveX和COM的開發支援兩種方式:
  1. 支援將已有的COM或者ActiveX空間引入到Qt的應用程式中
  2. 支援將Qt應用程式或者Qt的物件匯出成COM物件或者ActiveX控制元件供他人使用
具體來說,Qt是通過ActiveXQt框架中的兩個模組來支援上述所說的兩種方式的:
  1. 使用QAxContainer模組,通過QAxObject和QAxWidget分別支援COM物件和ActiveX控制元件的開發,可以通過這兩個物件將外部的COM或者ActiveX元件接入到Qt應用程式
  2. 使用QAxServer模組,通過QAxAggregated、QAxBindable和QAxFactory類,通過了程序內和可執行程式exe兩種方式的COM Server模式,用來將Qt寫的內容匯出為COM或者ActiveX供他人使用。
下圖簡要的說明了QtActiveX的作用
  • 使用QtActiveX建立COM或ActiveX Server
在正式開始之前先對COM和ActiveX做一個簡要的對比。COM(Component Object Model)是微軟提出的一種技術,它定義了一種規範,通過COM可以輕鬆實現一種語言(如C#)呼叫另一種語言(如C++、VB等)開發的功能模組。ActiveX是微軟主要針對網際網路客戶端設計的以COM為技術基礎的一種實現,一般來說二者並沒有本質的區別,僅有一些概念上的差異,一般來說: 1. ActiveX一般包含一個窗體介面,COM物件一般並沒有介面 2. COM物件一般作為一個可呼叫的模組來使用,ActiveX一般嵌入在網頁中使用 上述僅僅是一種使用上的慣例,但是並未強制一定這樣 使用Qt作COM和ActiveX的開發需要使用QAxServer模組,這裡麵包含三個類: 1. QAxFactory定義了建立COM物件的工廠類 2. QAxBindable定義了COM物件與Qt物件之間的轉換關係,也就是說Qt中的物件通過QAxBindable轉換為COM中的要素 3. QAxAggregated定義了COM元件介面
  • Qt作為Server支援的模式
COM元件在開發出來之後有多種形式,可以是一個dll,也可以是一個exe可執行程式。可以在程序中被載入(一般最常用的模式),可以作為外部程序為其他程序提供服務,甚至可以是遠端伺服器上的程序為本地整合提供服務。Qt ActiveX 提供了In-Process和out-of-process executable兩種方式的支援。簡單來說就是提供了一種 Dll供應用程式呼叫也提供了一種可以作為執行的exe,為其他應用提供一些呼叫服務。 當作為獨立exe時候,我們需要這樣編寫.pro檔案:
TEMPLATE = app
QT  += axserver

RC_FILE  = qaxserver.rc
當作為程序內dll的時候,需要這樣寫.pro檔案
TEMPLATE = lib
QT += axserver
CONFIG  += dll

DEF_FILE = qaxserver.def
RC_FILE  = qaxserver.rc
...
但是我們不用去操心這些內容,因為一般我們是用嚮導來生成Qt工程的,當你勾選ActiveX Server模式的時候,嚮導已經幫你寫好這些內容了。
當使用QAxServer開發dll時,實際工程編譯連結過程中會涉及到以下的過程: 1. 應用程式將會連結到qtserver.lib而不是qtmain.lib 2. idc工具會被呼叫,產生IDL檔案(介面描述語言的介面描述檔案) 3.呼叫MIDL工具編譯IDL檔案到型別庫 4.呼叫idc工具將型別庫附到server的二進位制程式碼中 5. 註冊dll 另外在.Pro檔案中可以新增一個版本資訊,這個版本資訊會作型別庫和server dll的版本號,新增方式使用VERSION變數即可:
TEMPLATE = lib
VERSION = 2.5
...
  • COM程序外和程序內使用方式的比較
程序外的使用方式是將COM寫成一個exe可執行檔案,當它執行的時候其他程式可以呼叫它提供的介面來開發,這樣做如果某個呼叫它的程式出了bug,那麼只有該程式會崩潰,其他呼叫不收影響,提供了更好的隔離性。缺點是需要更長的啟動時間與跨程序通訊的一些額外負擔。 程序內的使用方式就很簡單了,呼叫過程僅僅是通過類似虛擬函式這樣的呼叫,需要載入的時間短、效率比較高。
  • 開發Server的過程
為了使用Qt實現COM物件,我們必須使用一個QObject的子類,如果該子類是QWidget的子類,那麼這個COM物件就是一個ActiveX控制元件。程式碼如下:
#include <QWidget>

class MyActiveX : public QWidget
{
    Q_OBJECT
只裡面Q_OBJECT巨集不能少,它提供了關於類MyActiveX的一些元資料資訊, 接下來繼續新增ActiveX的一些資訊
Q_CLASSINFO("ClassID", "{1D9928BD-4453-4bdd-903D-E525ED17FDE5}")
Q_CLASSINFO("InterfaceID", "{99F6860E-2C5A-42ec-87F2-43396F4BE389}")
Q_CLASSINFO("EventsID", "{0A3E9F27-E4F1-45bb-9E47-63099BCCD0E3}")
Q_CLASSINFO定義了COM元件的一些資訊,這裡面ClassID和InterfaceID是必須的,當你需要使用COM中的事件時,EventsID就需要新增進來。裡面的128位的字串使用GUID.exe工具生成(微軟提供的一個小工具,可以生成全球唯一的識別符號,可以在系統資料夾或者visual Studio的工具選單中找到),工具提供了多種模式,可以選擇需要的模式複製新增到程式碼中:
除了上述基本必要的資訊之外(ClassID實際上被寫入登錄檔中,當控制元件被使用的時候會搜尋登錄檔找到裡面該COM控制元件dll的位置並載入,可以使用regedit.exe在HKEY_CLASSES_ROOT中找到很多已經註冊的COM元件資訊),Qt還提供了Q_CLASSINFO更多的內容,如下表所示:
名稱 值和含義
Version 類的版本號,預設值是1.0
Description 類的描述
ClassID                                                  類的ID(COM中用來唯一確定一個類的方式)
InterfaceID 介面ID(COM中用來唯一確定介面的方式)
EventsID 事件ID
DefaultProperty 預設屬性
DefaultSignal 預設的時間
LicenseKey 類的許可號,預設未開啟,如果開啟使用類需要許可號
StockEvents TODO:???
ToSuperClass 暴露父類的介面
Insertable 設定"yes"後可以被列到OLE2容器中,預設未設定
Aggregatable 預設是"yes",COM支援聚合
Creatable 設定為“no”呼叫者不能使用該類
RegisterObject 僅能用在程序外方式的COM中
MIME 該COM控制元件支援的檔案格式描述
CoClassAlias 類的名稱在IDL中被修改為CoClassAlias指定的名字

繼續上面的程式碼,接下來可以新增一些屬性到COM元件中,可以使用另一個巨集Q_PROPERTY
Q_PROPERTY(int value READ value WRITE setValue)
之後可以像寫Qt程式那樣來完成。
public:
    MyActiveX(QWidget *parent = 0)
    ...

    int value() const;

public slots:
    void setValue(int v);
    ...

signals:
    void valueChange(int v);
    ...

};
Qt的ActiveX框架會將Qt類中的要素轉換為COM中的標準要素供其他呼叫者使用,具體來說:  1.  Qt類中的屬性和公有的插槽函式(slots)會被轉換為COM中的屬性和方法  2.  Qt類中的訊號(signals)會被轉換成為COM元件中的事件

另外其他的資料型別轉換之間的對應關係如下圖所示:  1. Qt中屬性的資料型別與COM中資料型別的轉換關係如下:
Qt資料型別 COM 屬性資料型別
bool VARIANT_BOOL
QString BSTR
int int
uint unsigned int
double double
qlonglong CY
qulonglong CY
QColor OLE_COLOR
QDate DATE
QDateTime DATE
QTime DATE
QFont IFontDisp*
QPixmap IPixtureDisp*
QVariant VARIANT
QVariantList            SAFEARRAY(VARIANT)
QStringList SAFEARRAY(BSTR)
QByteArray SAFEARRAY(BYTE)
QRect User defined type
QSize User defined type
QPoint User defined type

2. Qt中訊號和插槽函式的形式引數資料型別
Qt資料型別 對應COM的資料型別
bool [in] VARIANT_BOOL
bool& [in,out] VARIANT_BOOL*
QString, const Qtring& [in] BSTR
QString& [in, out] BSTR*
QStinrg& [in, out] BSTR*
int [in] int
int& [in, out]int
uint [in,out]unsigned int
uint& [int, out] unsigned int*
double [in] double
QColor, const QColor& [in] OLE_COLOR
QColor& [in,out] OLE_COLOR*
QDate, const QDate& [in] DATE
QDate& [in,out]DATE*
QDateTime, const QDateTime& [in] DATE
QDateTime& [in,out]DATE*
QFont, const QFont& [in] IFontDisp*
QFont& [in,out]IFontDisp**
QPixmap, const QPixmap& [in]IPictureDisp*
QPixmap& [in,out]IPictureDisp**
QList<QVariant> [in]SAFEARRAY(VARIANT)
QList<QVariant>& [in,out]SAFEARRAY(VARIANT)*
QObject* [in] IDispatch*
此外QActiveX也可以通過Q_ENUMS和Q_FLAGS將列舉變數匯出,如果使用了非上表中的型別來作為訊號和插槽函式的引數型別,那麼Qt ActiveX框架不會轉換這些引數。
  • 釋出COM控制元件
為了讓Qt編寫的程式碼可以作為一個COM元件被使用,必須提供獲取COM物件的方法,在Qt中使用QAxFactory來完成,最簡單的方法是使用下面的巨集:
QAXFACTORY_BEGIN("{ad90301a-849e-4e8b-9a91-0a6dc5f6461f}",
                 "{a8f21901-7ff7-4f6a-b939-789620c03d83}")
    QAXCLASS(MyWidget)
    QAXCLASS(MyWidget2)
    QAXTYPE(MySubType)
QAXFACTORY_END()
上面這段程式碼把MyWidget和MyWidget2匯出為可供外部呼叫的COM物件,並註冊MySubType型別可供MyWidget和MyWidget2中的屬性和引數使用。
  • 程序外COM控制元件的編寫
對於程序外可執行的COM元件,我們需要執行一個main函式來例項化一個QApplication,和一般的Qt程式類似。
#include <QApplication>
#include <QAxFactory>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    if (!QAxFactory::isServer()) {
        // create and show main window
    }
    return app.exec();
}