作者:胎神
原帖來源:【VB】 在VB6IDE中實現(xiàn)安全子類化? 文章鏈接會被屏蔽么鹤,請自己搜索?0xaa55?或?技術(shù)宅的結(jié)界?(出處:技術(shù)宅的結(jié)界,轉(zhuǎn)載請保留出處?)
文章最后有?源碼示例下 載服傍。
VB6給我們開發(fā)者提供了非常簡單方便的GUI設(shè)計功能暇昂,因此受到了大多數(shù)開發(fā)者的喜愛“槲耍可是VB6提供的使用功能卻非常少急波,這個時候就通過子類化等手段繞過VB6的封裝,直接取接管底層的系統(tǒng)消息來對其擴(kuò)展瘪校。
然而澄暮,VB6IDE的作者當(dāng)初犯了一個非常胎神的錯誤,那就是讓用戶編寫的代碼直接在IDE進(jìn)程中運行了阱扬,沒有通過子進(jìn)程+進(jìn)程調(diào)試模式(有的胎神看到這里會反駁說著就是VB6優(yōu)勢不用花大量時間去先編譯代碼泣懊,我表示VB6作者完全可以設(shè)計一個叫 解釋器.exe 的獨立可執(zhí)行程序到VB6安裝包中,在我們要調(diào)試運行的時候麻惶,它直接發(fā)代碼文本發(fā)送給這個子進(jìn)程不就行了馍刮,這樣不就既有解釋運行的快速啟動,又有不崩IDE的風(fēng)險嗎窃蹋?)卡啰,這就會導(dǎo)致程序一旦出現(xiàn)致命錯誤就把整個IDE給崩掉了(很多開發(fā)者這個時候都還沒來得及保存代碼)。
言歸正傳警没,現(xiàn)在假如某個開發(fā)者本身技術(shù)確實比較牛批匈辱,不容易寫出本身有致命錯誤的代碼,但是在Form杀迹、控件被子類化的時候亡脸,VB6本身是回調(diào)機(jī)制就已經(jīng)自帶了極易導(dǎo)致致命錯誤的風(fēng)險(哪怕你本身代碼寫的再好),只要不小心下了斷點树酪、點了暫停浅碾、結(jié)束運行,或有沒有On Error的異常都會導(dǎo)致VB6IDE崩掉续语。
經(jīng)過我多年的研究終于搞清楚了子類化極易導(dǎo)致IDE崩掉的根本原因:
當(dāng)我們直接點擊IDE的<按鈕或用End語句結(jié)束執(zhí)行的時候垂谢,VB6釋放掉了AddressOf的地址,但是我們沒有代碼去及時移除子類化绵载,系統(tǒng)會仍然往已釋放的地址上回調(diào)埂陆。
當(dāng)我們在點擊IDE的;按鈕或打上斷點觸發(fā)暫停的時候苛白,這時VB6雖然還沒有釋放AddressOf的地址娃豹,但是當(dāng)有新的窗口消息來時,會導(dǎo)致IDE進(jìn)入運行狀態(tài)直到該函數(shù)返回再重新恢復(fù)暫停狀態(tài)购裙,但是如果在這里如果又觸發(fā)了再次斷點懂版,就會發(fā)生和恐怖的事情:在斷點中又去執(zhí)行新的消息、新的消息有觸發(fā)斷點躏率、新的斷點又能接收更新的消息…躯畴。最后因為一直遞歸把堆椕窆模空間消耗完了蓬抄,導(dǎo)致了堆棧溢出錯誤丰嘉,結(jié)果在提示堆棧溢出的錯誤對話框的消息循環(huán)中還在繼續(xù)觸發(fā)子類化消息回調(diào),最終導(dǎo)致了IDE進(jìn)程崩掉嚷缭。
在正常情況下的代碼出現(xiàn)錯誤時饮亏,VB6會彈出錯誤提示對話框,這時我們可以選擇 調(diào)試 或 停止 兩個選項阅爽,選擇 停止 會觸發(fā) 1 的流程路幸,選擇 調(diào)試 則觸發(fā) 2 的流程,最終還是會導(dǎo)致IDE進(jìn)程崩掉付翁,最蛋疼是報錯對話框是模態(tài)简肴,IDE主窗口被鎖住了,這時沒法點擊保存百侧。
為了解決以上各種蛋疼問題砰识,我選擇了使用加載外部dll方案,因為:
只要子類化回調(diào)函數(shù)的dll沒有被FreeLibrary佣渴,回調(diào)函數(shù)的地址就始終有效仍翰。
即便VB6IDE進(jìn)入暫停模式,外部dll并沒有被暫停观话,它仍然可以正常工作(只要這時候它只做自己的工作予借,不回調(diào)給VB6IDE的臨時代碼就行,我在這時的做法就是判斷IDE在暫停狀態(tài)就直接執(zhí)行默認(rèn)處理频蛔,運行狀態(tài)才回調(diào)給VB6)灵迫。
dll使用COM接口機(jī)制,可以保證VB6會正確的自動釋放對象(哪怕是直接點擊IDE的<按鈕或用End語句結(jié)束執(zhí)行的時候)晦溪,這時我就可以利用COM對象的析構(gòu)函數(shù)去實現(xiàn)自動的安全移除子類化監(jiān)聽回調(diào)(注意:在VB6中End后瀑粥,IDE自身類模塊中的析構(gòu)函數(shù)是不會再執(zhí)行的,但外部dll不存在這個問題三圆,仍然會正確執(zhí)行)狞换。
順便再說一下傳統(tǒng)的SetWindowLong的子類化方式的缺陷:
SetWindowLong 不兼容64位環(huán)境
SetWindowLong 沒有掛鉤順序管理:假如a先子類化b后子類化,就必須要b先還原子類化a后還原子類化才能正確執(zhí)行舟肉。如果a先還原子類化修噪,這時b就收不到任何消息了,等到b還原子類化的時候卻把a(bǔ)的回調(diào)地址給設(shè)置上去了路媚,使得a重新開始接收了子類化消息(但是這時a的地址是有可能已經(jīng)釋放掉了)黄琼。
SetWindowLong 不能給回調(diào)函數(shù)附加額外數(shù)據(jù),回調(diào)函數(shù)里面只能收到hWnd句柄整慎,但沒法直到hWnd句柄對應(yīng)的是哪個Form或哪個控件(這里肯定又有胎神會提到 SetWindowLong hWnd, GWL_USERDATA, ObjPtr(控件對象) 的操作了脏款,但是想想如果有 a 和 b 兩個人都子類化了同一個窗口會怎樣围苫?)。
其實SetWindowLong的缺陷巨硬早在二十多年就給我們提供了解決方案撤师,使用新API SetWindowSubclass 來子類化(SetWindowSubclass 保證了32位和64位通用剂府、安全管理了多重掛鉤,支持了任意順序解除剃盾、并且提供了指針長度的任意附加數(shù)據(jù))周循。
所以,最后我根據(jù)以上原理万俗,使用VC艸封裝一個叫 sscls.dll(全稱 SafeSubclass 安全子類化)的組件湾笛,內(nèi)部使用輕量級COM機(jī)制來實現(xiàn)既不需要注冊又能獲得COM對象自動安全析構(gòu),使用新子類化API SetWindowSubclass 來保證多次子類化的安全和回調(diào)對象關(guān)聯(lián)闰歪,調(diào)用VBA6.DLL的EbMode來監(jiān)測IDE執(zhí)行狀態(tài)嚎研,以防止暫停和停止時極易崩掉IDE的問題。
以下則是 SafeSubclass(安全子類化)組件 的使用方法:
1-打開VBAIDE目錄
2-復(fù)制到VB6安裝目錄
3-在VB6工程中打開引用對話框
4-添加sscls.tlb文件的引用
5-編寫代碼進(jìn)行子類化
6-可以在消息中任意暫停
7-強(qiáng)制結(jié)束也可以正常退回IDE設(shè)計模式并自動釋放子類化對象的內(nèi)存
8-示例目錄中的sscls.dll用于最終發(fā)布的exe攜帶
下載:胎神作品SafeSubclass.rar
鏈接: https://pan.baidu.com/s/1jpvJRhd-Zs4VskSCQuTMHQ?pwd=rjtj 提取碼: rjtj
如對您有幫助库倘,可點個贊及關(guān)注我