【大型軟體開發】淺談大型Qt軟體開發(四)動態連結庫的宏衝突問題、COM元件開發的常見問題

2023-02-10 21:00:56

最近工作的時候有一個連結庫的對接工作,在對接時發生了一些小問題,這篇FAQ是辦公室寫這個庫的工程師戴工寫的,這裡記錄一下:

一、編譯工程時報連結錯誤「不允許dllimport靜態資料成員的定義」

1.錯誤截圖

2.錯誤原因分析

此錯誤是Q_OBJECT和Q_DECL_IMPORT宏共同作用時產生的結果。查詢微軟檔案可知:靜態資料成員無法在定義dllimport類的同一程式中指定定義。

這句話表明,匯出類時,其靜態成員不可被外部覆蓋定義。舉個例子,有匯出類如下:

其中宏定義為:

根據微軟關於dllimport的說明,類靜態成員變數n不可被覆蓋定義,即進行這樣的操作

可以看到,對普通成員函數fun進行覆蓋,僅僅發出了編譯警告,但編譯器並沒有明確拒絕這種行為,但在對靜態成員n進行覆蓋定義時,編譯器報錯,拒絕了此行為。

回過頭來看Q_OBJECT宏的定義:

發現靜態成員staticMetaObject,根據QMAKE規則,QT編譯時會對包含有Q_OBJECT宏的類進行一次moc工作,而moc_*.cpp中剛好擁有staticMetaObject的定義操作:

因此,Q_DECL_IMPORT(即dllimport)宏匯出的類中包含了Q_OBJECT宏時,違反了編譯器規則,因此無法完成編譯。

3.修改策略

(1)修改匯出類,如果匯出類中含有Q_OBJECT宏,保留匯出定義,去除匯入定義;
(2)去除Q_DECL_IMPORT宣告。由QT嚮導生成的宏宣告檔案中,有條件編譯控制宏BUILD_STATIC,當工程中定義了該宏時,便可取消Q_DECL_IMPORT宏的匯入宣告;也可以不定義BUILD_STATIC宏,而是手動修改檔案,去除Q_DECL_IMPORT宏。

(3)去除/註釋Q_OBJECT宏。匯入類時,可以在工程程式碼中將匯入庫標頭檔案中的Q_OBJECT宏逐個註釋。

(4)從工程中移除相關標頭檔案。

二、建立dll匯入類時報警告「QObject: Cannot create children for a parent that is in a different thread.」

1. 錯誤截圖

2.錯誤原因分析

這可能是由於工程編譯版本型別與連結庫的型別不一致導致,比如工程當前為debug版本,但連結庫編譯生成版本為release

3.錯誤修改策略

將工程生成版本調整為與連結庫版本一致

三、單例寫法:

原先的單例寫法比較簡單,程式碼大概如下:

static Test&Test::Singleton(){
	static Test Instance;
	return Instance
}

這樣的單例模式看上去還行,這個也是我之前常用的單例模式。但是這個在COM元件的開發中是不夠安全的,原因也很簡單~因為我們提交給COM元件的IDispatch是指標形式提交的,這個指標有可能會在外部被解構掉(只是提出一種可能,這裡尚未測試和驗證),所以這個單例模式是指標不安全的。

既然如此我們就需要修改一種比較好用且指標安全的單例模式。今天看了下小余的程式碼我感覺還挺好用的,拿過來抄一下:

static NetServer* NetServer::Singleton() {
	static QMutex mutext;
	static QSharedPointer<NetServer> inst;
	if (Q_UNLIKELY(!inst)) {
		if (!inst) {
			inst.reset(new NetServer());
		}
	}
	return inst.data();
}

這段程式碼就是用的QSharedPointer 智慧指標對單例指標進行維護,這樣只要主程序還在,這個單例的指標就會一直儲存在QSharedPointer內,就不會被解構了,除非你自己提供了一個解構的方法。