由於最近需要使用ActiveX,一般來說可以使用微軟提供的MFC或者ATL框架來開發,由於我個人對這部分內容不是很熟悉,好在Qt也提供對於ActiveX的支持。本文主要記錄個人學習ActiveX的一些內容,方便日后查閱。本文以Qt5(5.3.1)提供的ActiveX為參考,但是由於ActiveX這部分比較穩定,因此Qt4應該也是一樣的。
- 概述
Qt提供了QtActiveX模塊來支持微軟ActiveX的開發,Qt的ActiveX和COM的開發支持兩種方式:
- 支持將已有的COM或者ActiveX空間引入到Qt的應用程序中
- 支持將Qt應用程序或者Qt的對象導出成COM對象或者ActiveX控件供他人使用
具體來說,Qt是通過ActiveXQt框架中的兩個模塊來支持上述所說的兩種方式的:
- 使用QAxContainer模塊,通過QAxObject和QAxWidget分別支持COM對象和ActiveX控件的開發,可以通過這兩個對象將外部的COM或者ActiveX組件接入到Qt應用程序
- 使用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
- TEMPLATE = lib
- QT += axserver
- CONFIG += dll
- DEF_FILE = qaxserver.def
- RC_FILE = qaxserver.rc
- ...
當使用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_CLASSINFO("ClassID", "{1D9928BD-4453-4bdd-903D-E525ED17FDE5}")
- Q_CLASSINFO("InterfaceID", "{99F6860E-2C5A-42ec-87F2-43396F4BE389}")
- Q_CLASSINFO("EventsID", "{0A3E9F27-E4F1-45bb-9E47-63099BCCD0E3}")

除了上述基本必要的信息之外(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)
- public:
- MyActiveX(QWidget *parent = 0)
- ...
- int value() const;
- public slots:
- void setValue(int v);
- ...
- signals:
- void valueChange(int v);
- ...
- };
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框架不會轉換這些參數。
- QAXFACTORY_BEGIN("{ad90301a-849e-4e8b-9a91-0a6dc5f6461f}",
- "{a8f21901-7ff7-4f6a-b939-789620c03d83}")
- QAXCLASS(MyWidget)
- QAXCLASS(MyWidget2)
- QAXTYPE(MySubType)
- QAXFACTORY_END()
- #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();
- }
http://blog.csdn.net/csxiaoshui/article/category/5705027
http://download.csdn.net/detail/csxiaoshui/9728972