Qt開發Active控制元件:如何使用ActiveQt Server開發大型軟體的主框架
注:本文更多地是帶著如何去思考答案,而不是純粹的放一個答案上來,如果你需要直接看到完整的答案,請直接看例項和最後的柳暗花明部分,裡面由詳細的註釋可以解答你的問題。
前情提要:
Qt的程式間通訊,以伺服器的形式,手把手教你VS上進行Qt的COM、ActivedQt Server的開發,比保姆還保姆
本文提供兩個例項:
ActiveQt Server COM伺服器端:Qt_ActiveServer_Main
ActiveQt Active控制元件呼叫端:Client_ActiveServer_Demo
上文提到如何建立並呼叫一個Active控制元件,但是並不能完全滿足我們的要求:現在我有一個主框架或者說主程式,會有很多別的模組需要在外部開發,這些模組可能會需要用到主框架中的資訊,或者說一些介面來呼叫一些特定的功能。比如說我一個課堂教學模組,可能需要實現螢幕廣播,語音廣播,資料下發等功能。
如果你根據前文的例項進行我們根據上文的例項開發中會發現一個很明顯的問題,那就是我們可以從呼叫方向主框架呼叫方法,但是卻沒法從主框架中傳送訊息給呼叫方。或者說如果這個COM伺服器是有ui的,如果多個呼叫方可能就會調起多個介面,而且關閉呼叫方的同時會把這些介面都關閉,這又是怎麼回事呢?為什麼我們的ActiveServer不是像一個伺服器一樣簡單地向其他的呼叫方提供服務?
關於這點,我翻遍了國內外幾乎所有論壇和文章,以及Qt官方論壇和文章,發現並沒有什麼討論的,或者零星有兩個提問的人,但是都沒有獲得相關開發人員的回應。在這裡我將會討論上面的問題,並給出一套可行的方案。
一、當我們在建立一個ActiveQt Server的時候,我們在做什麼。
由前文我們可以知道,我們建立一個Qt的COM元件,其實很簡單,透過
這樣一個宏就可以將一個類暴露在外,並且建立連線。然後我們可以拆分成如下形式:
--------------與以下形式---------------
我們在main函式內這麼寫了之後,我們就可以來呼叫了。這是我們在Qt的官方文件上看到的,也是我們能在幾乎所有資料上看到的。
現在我們是不是建立了一個Active Server了呢?是的,你是建立了一個ActiveServer,但是現在這個ActiveServer只能單方面地提供服務,就像我上面說的那樣:,那就是我們可以從呼叫方向主框架呼叫方法,但是卻沒法從主框架中傳送訊息給呼叫方。
這是為什麼?我們要從建立開始說起。我們知道ActiveQt Server上是透過三個模組來實現ActiveServer的,我們可以從官方文件上看到:
第一個 第二個類你稍微看一下內容你就會發現,第一個類是為了提供一些額外介面,第二個類是用來提供與COM事件和繫結函式、引數的。真正用於運作整個ActiveServer的其實是QAxFactory
但是我們走進來看這個類的介紹,我們發現這個類裡面全是提供的虛擬函式,我們應該會意識到這個類其實是一個基類,透過虛擬函式提供了一大堆的方法供其他元件去呼叫
聰明的你肯定想到了,那既然如此肯定是需要我們去繼承這個QAxFactory類,然後來執行一些操作的吧!
等等,我們真的需要繼承一個QAxFactory類(且不論能不能繼承)嗎?我們好像並沒有見到一個QAxFactory的例項吧,那我們繼承它又有什麼用呢?(當然了,其實我是嘗試繼承了的,但是繼承不了,這個單例會直接無法初始化了,還有些別的問題,這裡就不一一分析了)
文件中沒有提到,但是我在某個不記得的帖子上看到有人在說QAxFactory類提供了一個全域性的單例qAxFactory(),於是我開始研究起了這個單例
經過一段時間對QAxFactory類的函式的研究,我發現這些函式其實沒有多大用,並沒有想到能解決什麼問題的,於是問題又繼續了下去。
有什麼,不一樣?
我們這個時候想到去比對一下,我們生成的ActiveServer和普通的Qt應用程式有什麼不一樣?聰明的你發現了,除了引用了QAxFactory.h之外,就是引用了這個宏:
----------------分割線-----------------
在中文
透過查詢資料,你會發現Q_CLASSINFO宏只是給類提供了一個標識,並不是影響工作的關鍵。於是QAXFACTORY_BEGIN就理所應當的是我們查詢的關鍵,讓我們走到官方文件:(英文的就不放了,看著費勁,這裡放個百度翻譯吧)
這個好像和我之前講的也沒有什麼關係,只是影響了宣告匯出和註冊的類等資訊,為了探尋真相,我們需要進一步走到QAxFactory.h內部去看發生了什麼
這個宏好像就覆寫一堆函式...有點意義不明,繼續看下去,然後就想了一段時間....
想一下發生了什麼
我們發現,每次一個外部程式用一個QAxObject來setControl一下我們的伺服器,就會多一個視窗對吧。等等,多一個視窗?
為什麼會多一個視窗?難道是建立了一個新的程式嗎?不會啊,還記得我們的Main函式是怎麼寫的嗎?
在我們的程式設計之初就考慮了不會讓外部程式啟動一個新程式的,所以不可能是新的程式導致的視窗啟動的。
那就只有一個可能了,是一個新的類例項化了。我們將斷點放到視窗類的建構函式上,我們發現斷點被擊中了!說明猜測是對的,類確實是被例項化了。
這樣一切的疑問就解決了!為什麼我們沒法通訊,因為訊號根本就不是從這個例項發出的,而是從另外一個被外部呼叫繫結的例項啊!都不是一個物件而且沒有進行繫結那肯定是沒法通訊的啊!
也就是說,每個QAxObject 繫結了與ActiveQtServer之間的連線的時候,都會例項化一個新的例項來專門處理這這兩個程式之間的服務與連線。
現在的問題就很簡單了,我們該如何找到這個被新建的例項?
我們可以根據呼叫堆疊找到,是透過這個QAxFactory類的某個例項呼叫了createObject。那麼問題就從怎麼建立連線變成了怎麼找到這個QAxFactory的某個例項建立的,於是我又回到了對qAxFactory()的研究
介紹文件中是這麼介紹QAxFactory類的:
我真的被這個文件瘋狂誤導,因為確實是呼叫了這個createObject 方法生成了一個例項(但是不是透過qAxObject進行的,後面會說),但是我硬是橫豎看不懂怎麼找到已經呼叫生成的例項我要去哪裡找,唯一有點關係的就是這個featurList() (其實一點關係沒有),搞了好久我覺得哪裡有問題,就又回到qaxfactory.h裡面去看,到底做了什麼
這次不一樣了,我看到一點不一樣的東西,我們看到這個宏,初始化了一個QAxClass,這個類是什麼?在Qt的官方文件上我居然沒有找到,定睛一看,哦原來是這個.h檔案裡面宣告的類,來看看內容
原來是提供了一個QAxFactory的模板子類,到這裡我才明白,原來這是透過這個QAxClass的createObject來生成例項的。於是我開始嘗試繼承重寫這個類
(過程不表了,反正搞了一個多小時我發現這個類其實是寫在lib裡面的改不了 囧)
ok重寫是不行的,那肯定也和這個qAxFactory()的例項沒有關係了,現在貌似又進了一個死衚衕。
最後柳暗花明的是,我抱著試一試的態度問了下AI,它居然告訴我,你既然要記錄例項化的物件,那你為什麼不透過一個靜態的QList,在每次這個類的物件例項化的時候將其記錄在案。當你需要傳送訊息的時候,就可以從這個QList中讀取想要的QObject,然後傳送對應的訊號,不就行了嗎?
聽罷,我驚為天人。於是就開始寫
柳暗花明-總結
於是總的開發就這樣結束了,把大概最後的框架描述一下就是
首先我們需要一個匯出類A,這個類是暴露給外部的,提供介面給外部程式呼叫,這個類A需要繼承QObject和QAxBindable,前者可以保證signals和slots的使用,後者可以保證signals和slots和COM函式和事件對接上。
然後我們要知道,外部每次呼叫setControl,其實都是例項化了一個匯出類A,與這個例項之間進行訊號交換。我們需要在這個類中宣告一個靜態陣列,用於每次例項化這樣一個物件的時候將所有的物件記錄在案,以供呼叫。
然後我們可以透過一個總的COM管理類Main_Activerserver_Demo來管理這些例項化物件,比如讓所有的物件傳送訊息
具體你想怎麼操作都行,這裡就不表了。
這裡你可能會問,那我怎麼讓這個管理類Main_Activerserver_Demo接收外部程式的訊息?它怎麼知道訊號什麼時候來,怎麼知道什麼時候有新的例項會來?當然了,我們不知道,於是只能藉助外部的一個訊號中轉站SignalCenter,並建立一個單例,以類似
的形式來廣播通知讓所有能看到SignalCenter的例項接收到訊息...目前的想法是這樣,也許後面會有更好的方案,但這個不是重點。
more?剩下的只有愉快的交流就完事了