【轉】OnDropFiles 可能無響應的問題
大多數程式都有接收拖放檔案的功能,即是用滑鼠把檔案拖放到程式視窗上方,符合格式的檔案就會自動被程式開啟。最近自己對編寫的程式增加了一個拖放檔案的功能,在 Windows XP、Windows Server 2003系統上拖放檔案功能正常,而在 Windows 7 系統上拖放檔案功能不管用,毫無反應。經過一番探討,順利解決,故對相關知識的吸收與實踐整合於此。
舉例實證:
OK,使用 Visual Studio新建一個簡單的對話方塊程式,將【對話方塊】-【屬性】-【行為】-【Accept Files】置為【True】後,再使用選單中【專案】-【類嚮導】新增對於拖放檔案訊息【WM_DROPFILES】的訊息對映處理方法,程式碼如下所示:
1 void CExampleDlg::OnDropFiles(HDROP hDropInfo)
2 {
3 UINT nCount;
4 TCHAR szPath[MAX_PATH];
5
6 nCount = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0);
7 if (nCount)
8 {
9 for (UINT nIndex = 0; nIndex < nCount; ++nIndex)
10 {
11 DragQueryFile(hDropInfo, nIndex, szPath, _countof(szPath));
12 MessageBox(szPath, _T("WM_DROPFILES"));
13 }
14 }
15
16 DragFinish(hDropInfo);
17
18 CDialogEx::OnDropFiles(hDropInfo);
19 }
生成可執行程式,並直接執行程式後,並拖放檔案於對話方塊上方,完全沒問題,程式接收到了【WM_DROPFILES】訊息,如下圖所示:
然而,找到生成的可執行程式,【右鍵】-【以管理員身份執行】後,你再拖放檔案於上圖所示對話方塊上,則不會彈出上圖所示【WM_DROPFILES】訊息框,即測試對話方塊視窗沒有接收到【WM_DROPFILES】訊息,上方的訊息處理方法程式碼就不會呼叫了,如下圖所示:
因此,問題的關鍵在於你有沒有【以管理員身份執行】程式;如果【以管理員身份執行】程式,則程式視窗接收不到【WM_DROPFILES】訊息;反之,直接執行程式,則可以接收之。這是為何呢?這與 Windows 7 的安全許可權機制體系有關,本文下半部分再講,先介紹:【解決Win7系統下以管理員身份執行的程式接收不到拖放檔案訊息[WM_DROPFILES]問題的方法】。
如本例簡單的對話方塊程式,在CExampleDlg::OnInitDialog() 方法中新增如下程式碼:
1 BOOL CExampleDlg::OnInitDialog()
2 {
3 CDialogEx::OnInitDialog();
4
5 ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
6 ChangeWindowMessageFilter(0x0049, MSGFLT_ADD); // 0x0049 == WM_COPYGLOBALDATA
7
8 // ChangeWindowMessageFilterEx(m_hWnd, WM_DROPFILES, MSGFLT_ALLOW, NULL);
9 // ChangeWindowMessageFilterEx(m_hWnd, 0x0049, MSGFLT_ALLOW, NULL); // 0x0049 == WM_COPYGLOBALDATA
10
11 // ::DragAcceptFiles(m_hWnd, TRUE); // 對話方塊程式可在其【屬性】-【行為】-【Accept Files】置為【True】,而不用呼叫此行。反之則可,兩者可選其一嘛~~~
12
13 // ..........
14
15 return TRUE;
16 }
另外提下,如果你自己建立視窗而不是建立對話方塊,則無法像對話方塊一樣可以在【屬性】-【行為】-【Accept Files】置為【True】以表示你的程式是否對訊息【WM_DROPFILES】有興趣處理,當然能不能接收得到,是另一外碼事,但這是前提。因此可在你的視窗初始化函式(如CFuckWnd::OnCreate)或其它適合的位置呼叫API介面:
VOID
DragAcceptFiles(
HWND
hWnd,
BOOL
fAccept);
ChangeWindowMessageFilter 與 ChangeWindowMessageFilterEx 這兩個 API 為 Win7 系統新增的API介面,而 WinXP 系統沒有這兩個介面。檢視MSDN可知,ChangeWindowMessageFilter 作用:從 UIPI【使用者介面特權隔離】訊息過濾器中增加或移除一個訊息。ChangeWindowMessageFilterEx 作用:為特定視窗修改 UIPI【使用者介面特權隔離】訊息過濾器。關於【使用者介面特權隔離】,本文下部分再講。
簡而言之,上述程式碼即是讓系統對該測試程式不再過濾掉【WM_DROPFILES】訊息和【WM_COPYGLOBALDATA】訊息(此訊息 Windows 未公開),那麼該測試程式程序或對話方塊視窗能接收到【WM_DROPFILES】訊息。
再次生成可執行程式,然後【以管理員身份執行】,程式視窗就可以接收到拖放檔案訊息【WM_DROPFILES】了。
另外,如果以 Release 方式生成可執行程式後,在 WinXP、Win2003 系統下執行,則會出現如下圖報錯提示:
原因是 WinXP 及 Win2003 系統使用者庫 USER32.dll沒有ChangeWindowMessageFilter、ChangeWindowMessageFilterEx這兩個介面,而 Windows 7 系統下,則新增之。由於WinXP、Win2003 系統安全機制不會攔截訊息,因此也就沒有必要執行上述新增的程式碼了。但若想該測試程式在WinXP、Win2003 系統下執行正常,且要在 Win7 系統下能正常接收上述訊息,可直接從 Win7 之 USER32.dll 庫中獲取上述 API 函式入口地址,在 Win7 系統下肯定是可以獲取到入口地址的;而在 XP 系統下就肯定獲取不到其入口地址,但沒關係,不會影響程式執行,亦不會報錯。程式碼如下:
在示例 CExampleDlg 類中新增如下方法程式碼: 1 BOOL CExampleDlg::ChangeWndMessageFilterOk(UINT nMessage, BOOL bAllow)
2 {
3 typedef BOOL (WINAPI * ChangeWindowMessageFilterOkFn)(UINT, DWORD);
4
5 HMODULE hModUser32 = NULL;
6 hModUser32 = LoadLibrary(_T("user32.dll"));
7 if (hModUser32 == NULL) {
8 return FALSE;
9 }
10
11 ChangeWindowMessageFilterOkFn pfnChangeWindowMessageFilter = (ChangeWindowMessageFilterOkFn) GetProcAddress(hModUser32, "ChangeWindowMessageFilter");
12 if (pfnChangeWindowMessageFilter == NULL)
13 {
14 FreeLibrary(hModUser32);
15 return FALSE;
16 }
17
18 FreeLibrary(hModUser32);
19
20 return pfnChangeWindowMessageFilter(nMessage, bAllow ? MSGFLT_ADD : MSGFLT_REMOVE);
21 }
更改為如下高亮處程式碼行:
1 BOOL CExampleDlg::OnInitDialog()
2 {
3 CDialogEx::OnInitDialog();
4
5 ChangeWndMessageFilterOk(WM_DROPFILES, MSGFLT_ADD);
6 ChangeWndMessageFilterOk(0x0049, MSGFLT_ADD); // 0x0049 == WM_COPYGLOBALDATA
7
8 // ..........
9
10 return TRUE;
11 }
接下來生成可執行程式,在任何系統環境下,均能執行正常,且能接收到【WM_DROPFILES】訊息了。
Windows 7: Message Integrity Check(訊息完整性檢查)
訊息完整性檢查(Message Integrity Check),是 Windows 7 增加的 Windows 安全物件訪問控制安全機制,系統利用完整性級別對一個安全物件進行標記,通過降低程序的完整性級別可以限制其對安全物件的寫入許可權,這一點類似於使用者帳戶組的成員被限制訪問系統元件這種方式。完整性檢查機制使得用更少的許可權或以更低的完整性級別執行一些程式,會降低程序修改系統或損害使用者資料檔案的可能性。在 Windows 7 中訊息完整性檢查分為 6個等級,如表 1 所示:
MIC等級 | 說明 |
---|---|
SECURITY_MANDATORY_UNTRUSTED_RID | 不信任的MIC等級 |
SECURITY_MANDATORY_LOW_RID | 低MIC等級,如IE |
SECURITY_MANDATORY_MEDIUM_RID | 中MIC等級,預設為這個等級,如Explorer |
SECURITY_MANDATORY_HIGH_RID | 高MIC等級,以管理員身份執行的程式 |
SECURITY_MANDATORY_SYSTEM_RID | 系統MIC等級,一般是服務應用程式 |
SECURITY_MANDATORY_PROTECTED_PROCESS_RID | 被保護程序的MIC等級 |
MIC 等級的獲取是和建立該物件的程序完整性級別相關的。比如在 Windows 7 系統中 Explorer.exe 的完整性級別是中 MIC 等級,因為許可權繼承的關係,其啟動的程序具有的 MIC 等級也都預設是中 MIC 等級。一個安全物件的 MIC 等級由其訪問控制列表項中的某一項標識,可以使用工具 Process Explorer 檢視一個程序的 MIC 等級。
Windows7 系統正常啟動後,Explorer.exe 程序的 MIC 等級預設為:中 MIC 等級,如下圖所示:
Windows 7:User Interface Privilege Isolation (使用者介面特權隔離)
使用者介面特權隔離(User Interface Privilege Isolation),則是 Windows 7 通過 MIC 機制新引入的一種安全特性,用於攔截接收比自身程序 MIC 等級低的程序發來的訊息。UIPI 的目的是為了規範不同程序視窗之間的視窗訊息處理過程,預設情況下,高許可權程序不會接收到低許可權程序傳送的視窗訊息的,但是低許可權程序能夠接收到高許可權程序的視窗訊息。UIPI 的本質是系統檢查目標視窗和傳送方是否具有相同的 MIC 等級或者傳送方具有更高的 MIC 等級,如果符合上述條件,則允許訊息的傳遞,否則將訊息丟棄。
因此,在 Windows 7 作業系統中執行的使用者程序,如果執行時具有不同的完整性等級,即具有不同的 MIC 等級,那麼相互間的通訊將會無法像 Windows XP 那樣正常進行。
Windows 7: 高低許可權程序通訊的實現
通過以上分析可以看出,Windows 7 為了保證系統的安全性,嚴格地限制了高低許可權程序間的通訊。以下就程序是否屬於同一個會話給出 Windows 7 高低許可權程序通訊的方案。
基於 Windows XP 應用程式經常需要互相之間能傳遞訊息,Windows Vista 引入了 Change WindowMessageFilter(UINT message, UINT dwFlag)程式設計介面,用來新增或刪除隔離級別的訊息。Windows 7 引入一個新的程式設計介面 ChangeWindowMessageFilterEx(HWND hWnd, UINT message, DWORD action,PCHANGEFILTERSTRUCT pChangeFilterStruct),事件的引數可以是 MSGFLT_ALLOW、MSGFLT_DISALLOW 和 MSGFLT_RESET,將視窗重設成預設篩選器,可選的架構允許操作結果以接收更多的資訊。通過在高許可權程序中對指定的某一個低許可權程序所建立的視窗進行這樣的設定,則低許可權程序就可以通過發訊息的形式與高許可權程序通訊了。以下是在 Windows 7 下兩個程序通過視窗訊息通訊的測試結果:
傳送方程序許可權 | 接收方程序許可權 | 程序通訊結果 |
---|---|---|
低 | 低 | 成功 |
中 | 中 | 成功 |
高 | 高 | 成功 |
低 | 中 高 |
失敗 |
中 | 高 | 失敗 |
中 | 低 | 成功 |
高 | 中 低 |
成功 |
低 | 中/高+ChangeWindowMessageFilterEx | 自定義訊息成功,系統訊息失敗 |
中 | 高+ChangeWindowMessageFilterEx | 自定義訊息成功,系統訊息失敗 |
問世間,explorer.exe 程序為何物?
explorer.exe程序是微軟為其Windows作業系統定義的的系統核心程序,它是較老的Windows 3.x檔案管理器的替代品,在後來的系統中微軟將其稱為“Windows資源管理器”。在功能上,explorer.exe程序為使用者提供了圖形使用者介面(也稱為圖形殼),簡單的說就是用來顯示系統的桌面環境的,包括開始選單、桌面下方的工作列、桌面圖示和檔案管理。
【檔案管理】為explorer.exe程序眾多功能之一,平常我們雙擊桌面上的程式快捷方式或從資源管理器裡雙擊某個檔案以開啟之,一般explorer.exe程序便會建立相應的程式程序,也就是說explorer.exe程序是你手動開啟的程式程序的父程序。
OK,迴歸上述例項,再加闡釋:
到此時,我們已瞭解到MIC【訊息完整性檢查】、UIPI【使用者介面特權隔離】、【Windows 7 高低許可權程序通訊的實現】等觀念。且我們已知曉explorer.exe 程序為何物,在本文例項中,那麼我們從【桌面】或從【資源管理器】裡用滑鼠拖放檔案到程式視窗上,其實就是【explorer.exe】和【測試[WM_DROPFILES]訊息.exe】兩個程序之間的通訊,亦即【explorer.exe】向本例【測試[WM_DROPFILES]訊息.exe】傳送【WM_DROPFILES】訊息。在 XP 系統下,兩者無任何代溝,能順利收發訊息;而在 Win7 系統下,彼此能夠收發訊息,則沒那麼容易了,導致收發訊息不暢的“第三者”便是MIC和UIPI。
情況一、explorer.exe程序MIC等級為:中MIC等級,例項程式程序MIC等級為:中MIC等級,兩者相同
在Win7系統下,找到本例項生成的可執行程式,直接雙擊執行:測試[WM_DROPFILES]訊息.exe,用【Process Explorer】檢視此程序的MIC等級:中MIC等級,和Win7系統正常啟動後的Explorer.exe程序的MIC等級相同,則能成功從Explorer.exe程序管理的桌面或資源管理器中拖放檔案到本例項程式視窗上,亦即本例項程式能正常接收到【WM_DROPFILES】訊息並能處理之了。
情況二、explorer.exe程序MIC等級為:中MIC等級,例項程式程序MIC等級為:高MIC等級
在Win7系統下,找到本例項生成的可執行程式,右鍵【以管理員身份執行】,檢視MIC等級為:高MIC等級,然後從資源管理器中拖放一個檔案到本例項程式視窗,由於本例項程式程序MIC等級高於explorer.exe程序MIC等級,因此explorer.exe程式傳送到本例項程式的訊息,會被UIPI攔截掉,導致本例項程式無法接收之。
此種情況,要想接收到低MIC等級程序發來的拖放檔案訊息,則可以通過ChangeWindowMessageFilter在UIPI【使用者介面特權隔離】訊息過濾器中新增【WM_DROPFILES】【WM_COPYGLOBALDATA】這兩個訊息,注意其訊息過濾器相當於白名單,新增到過濾器,那麼傳送給對方程序的訊息不會被UIPI機制攔截掉,會被放行,從而名正言順地到達對方程序,而被接收處理之。具體程式碼在本文上半部分已講述。
情況三、explorer.exe程序MIC等級為:高MIC等級,例項程式程序MIC等級為:高MIC等級
在Win7系統下,找到本例項生成的可執行程式,右鍵【以管理員身份執行】,檢視MIC等級為:高MIC等級在【Windows 7: Message Integrity Check(訊息完整性檢查)】一節中,正常啟動的explorer.exe程序MIC等級為中MIC等級,如果把其MIC等級調為高MIC等級,那麼從explorer.exe所管理的資源管理器中拖放檔案到本例項程式視窗,由於等級同為高MIC等級,則訊息不會被UIPI攔截了。那麼開啟工作管理員,先結束掉explorer.exe程序後,再在【工作管理員】-【檔案】-【新建任務(執行…)】(如下圖所示),則再從資源管理器中拖放檔案到本例項程式視窗,也能正常被接收處理之。
結語:
對於上述情況一,如果你的程式不需要【以管理員身份執行】,則不需要額外操作即可正常接收拖放檔案訊息。
對於上述情況二、若程式需要【以管理員身份執行】,則另外需要新增額外程式碼(本文上半部分所述)修改UIPI訊息過濾器以實現拖放檔案訊息的正常接收處理。
對於上述情況三、只為實驗除錯MIC等級高低來驗證MIC等級機制,現實程式設計中,不好結束系統explorer.exe程序,再以管理員身份執行explorer.exe來調高其MIC等級,以免影響使用者體驗。
轉載於:https://www.cnblogs.com/ericdm/p/8675960.html