日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

一个分析“文件夹”选择框实现方法的过程

發布時間:2023/11/27 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一个分析“文件夹”选择框实现方法的过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ? ? ? 在軟件開發中,我們如果存在“導入導出”的場景時,難免會用到“文件夾”選擇框。之前一直沒有太關注過這個的實現過程。最近在工作中遇到了一些問題,我做了一些研究。在此記錄下研究的過程。(轉載請指明出于breaksoftware的csdn博客)

? ? ? ? 首先,我們發現我們的文件選擇框,只能顯示出本地文件夾,而不能顯示設備虛擬出來的文件。比如


? ? ? ? 這樣的設備,就不會在我們的文件選擇框中出現。


? ? ? ? 我們看下我們代碼中的設置

BROWSEINFOA bi;   
bi.hwndOwner      = hWnd;
bi.pidlRoot       = NULL;  
bi.pszDisplayName = NULL;   
bi.lpszTitle      = "請選擇下載位置";   
bi.ulFlags        = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;  
bi.lpfn           = BrowseCallbackProc;   
g_defaultfolder = WinTools::GetSystemPath(CSIDL_DESKTOP);
bi.lParam         = (LPARAM)(g_defaultfolder.c_str());
bi.iImage         = 0; 

? ? ? ??當時我的第一直覺就是我們的ulFlags設置的不對,然后我翻閱了MSDN,去掉了BIF_RETURNONLYFSDIRS就好了。

? ? ? ? 但是問題接踵而至


? ? ? ??當我們選擇了這個設備下的文件夾后,我們并不能獲取我們選擇的文件夾路徑。經調試發現是我們之后調用的獲取文件夾路徑的函數SHGetPathFromIDList返回失敗。
? ? ? ? 那我們就讓選擇框對這類文件進行過濾。當時我還是認為是不是我們哪個ulflags沒有設置。可是試了幾個感覺可能的flags,還是不行。

? ? ? ? 后來,我尋找到一個該功能完善的軟件A,它的展現是正確的。


? ? ? ? 最后我決定不再閉門造車,而是分析該軟件A這塊功能的具體實現。

? ? ? ? 首先我們要確認A軟件使用的哪個函數打開文件選擇框的。眾所周知,我們使用的SHBrowseForFolderA屬于SH類函數,即shell32.dll中的導出函數。SH類函數基本都是輔助類型函數,其是在windows原生API基礎上做了一層封裝。所以我們先要確定A軟件使用的是不是SHBrowseForFolder函數。我們使用Windbg附加到A進程上


? ? ? ? 其次,使用bp shell32!SHBrowseForFolderA 和bp shell32!SHBrowseForFolderW下函數斷點。一般來說,Windows平臺的API都有的A版和一個W版(有特殊的函數只有一個版本),所以我們在分析時,往往給A版和W版都下斷點。
? ? ? ? 最后運行掛起的A軟件,點擊“打開文件夾”。Windbg果然斷住了,這個證明A軟件使用的是SHBrowseForFolderW。


? ? ? ??這樣我們確定了軟件A是使用的SHBrowseForFolderW,那么我們開始分析,看看它是如何個這個函數的。這兒涉及一個稍微有點復雜的過程,因為A軟件很多地方是用.net寫的。我調回到調用SHBrowseForFolderW的地方,仍然難以直觀看到起參數的傳遞。我就改成分析SHBrowseForFolderW的實現,來查看其參數。我使用IDA,對Shell32.dll中的SHBrowseForFolderW進行逆向。以下列出其重要的代碼


? ? ? ??直到此時,我仍然認為我們的問題是出在flags設置不對上。所以我仍然只是關注了ulflags這個參數。我們看到我們可以在mov eax , [esi+10h]處看到ulflags的值。
? ? ? ? 回到windbg,使用u 7648dfae 7648dfff得到內存中的匯編代碼和地址(7648dfae是中斷下來后,得知SHBrowseForFolderW的入口地址)


? ? ? ??我們在7648dff5處下斷點(bp 7648dff5)。
? ? ? ? 中斷后我們用r eax指令查看eax的值


? ? ? ??我們終于拿到A軟件的ulflags的設置,本來以為大功告成。于是在代碼中將ulflags的值設置為0x40對應的宏。可是悲劇的是,問題依舊。看來并不是我們ulflags設置的不對。我們回到BROWSEINFO的參數說明。

typedef struct _browseinfo {HWND              hwndOwner;PCIDLIST_ABSOLUTE pidlRoot;LPTSTR            pszDisplayName;LPCTSTR           lpszTitle;UINT              ulFlags;BFFCALLBACK       lpfn;LPARAM            lParam;int               iImage;
} BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO;
? ? ? ??這些參數,除了ulflags對窗口的行為可能產生影響外,只能是lpfn了。我看了下我們的lpfn傳遞的是NULL, 而A軟件是否傳了值呢?
? ? ? ? 我們將斷點下在7648dffb,看看A軟件是否傳了值。

? ? ? ? A軟件傳遞了值!
? ? ? ? 那如何驗證是否就是這個回調函數導致了我們之間的差異?
? ? ? ? MSDN說明lpfn可以為NULL,那么我使用r eax=0來修改此處的eax,然后待7648dffb處指令執行完畢,就可以修改SHBrowseForFolderW內部使用了該回調地址的地方了。
? ? ? ? 修改好后,我們繼續執行A軟件,并選擇之前出現“確定”按鈕不可用的文件夾,可以看到這個時候的“確定”按鈕可用了。


? ? ? ? 于是原因找到了,此時我們只要關注該回調 如何實現便可以實現和A軟件的功能。

? ? ? ? 那么這個回調如何實現呢?我們看個網上很普及的例子

int CALLBACK BrowseCallbackProcSetting(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)  
{  if  ( BFFM_INITIALIZED == uMsg ) {  ::SendMessage(hwnd,BFFM_SETSELECTION,TRUE,lpData);  }  else if ( BFFM_SELCHANGED == uMsg ){char pszPath[MAX_PATH] = {0};LPITEMIDLIST pidl = (LPITEMIDLIST)(lParam);if ( NULL == pidl ) {return 0;}if (SHGetPathFromIDListA(pidl, pszPath)) {     ::SendMessage(hwnd, BFFM_ENABLEOK, 0, 1 );}else {::SendMessage(hwnd, BFFM_ENABLEOK, 0, 0 );}}else if ( uMsg == BFFM_VALIDATEFAILED ){return 1;}return 0; 
}
? ? ? ??這段代碼中,我們主要用的是BFFM_SELCHANGED == uMsg這段。
? ? ? ? 這段的主要思想是:用戶點擊的那個文件夾,我們可以獲取pidl,但是如果之后我們不能獲取pidl對應的文件夾路徑,我們的邏輯還是有問題。所有,在用戶點擊了一個文件夾后,我們在會立即檢查該文件夾的pidl是否可以拿到。如果可以拿到,那么我們就讓選擇框的OK按鈕置成可用,否則不可用。這種思想是預防于未來,我覺的還是很贊的。
? ? ? ? 但是這段代碼還是不健壯的。在win32位機子上,我們發現了一個特殊的場景:就是pidl可以獲得文件夾路徑,但是該文件夾不可訪問。導致我們設置后,無法打開這個文件夾,導致之后要將文件保存到該目錄下失敗。這個是個非常嚴重的問題。其實這個問題還是很常見的,我們永遠無法預測神奇的用戶詭異的行為:比如他把A目錄設置為只讀,然后通過我們程序去選擇這個目錄,導致我們無法成功在該文件夾下新建文件——因為該文件夾只讀。那么這個時候,我們需要做到:在用戶選擇時,判斷該文件夾我們是否可以寫入,如果可以寫入,則OK按鈕置為可用,否則置為不可用。
? ? ? ? 所以要將
if (SHGetPathFromIDListA(pidl, pszPath)) {      ::SendMessage(hwnd, BFFM_ENABLEOK, 0, 1 );
}
else {::SendMessage(hwnd, BFFM_ENABLEOK, 0, 0 );
}
? ? ? ??改成
if (SHGetPathFromIDListA(pidl, pszPath)) {     HANDLE hFile = CreateFileA(pszPath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL  );if ( INVALID_HANDLE_VALUE == h )  {::SendMessage(hwnd, BFFM_ENABLEOK, 0, 0 );}else {CloseHandle(hFile);hFile = NULL;::SendMessage(hwnd, BFFM_ENABLEOK, 0, 1 );}
}
else {::SendMessage(hwnd, BFFM_ENABLEOK, 0, 0 );
}
? ? ? ? 假如你認為一切已經大功告成,那就錯了。后來我們又發現,“新建文件夾”按鈕無法和“確定”按鈕同步。

? ? ? ? 我目前還沒找到一個優雅的控制“新建文件夾”按鈕的方法,只能通過枚舉子窗口,同時在子窗口中尋找“(”和“)”來識別和控制“新建文件夾”按鈕。于是整套完成的流程是

int CALLBACK BrowseCallbackProcSetting(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)  
{  if  ( BFFM_INITIALIZED == uMsg ) {  ::SendMessage(hwnd,BFFM_SETSELECTION,TRUE,lpData);  }  else if ( BFFM_SELCHANGED == uMsg ){HWND hNewFloderButton = NULL; HWND hChild = GetWindow(hwnd, GW_CHILD);int nMaxCount = 64;while ( hChild && nMaxCount > 0 ) {// 控制循環次數,以免死循環,保守性編程nMaxCount--;WCHAR wszBuffer[MAX_PATH] = {0};int nCount = GetClassName( hChild, wszBuffer, MAX_PATH );std::wstring wstrClassName( wszBuffer, nCount); if ( 0 == wstrClassName.compare(L"Button") ) {memset(wszBuffer, 0, MAX_PATH);nCount = GetWindowText(hChild, wszBuffer, MAX_PATH);std::wstring wstrText(wszBuffer, nCount);// 不同操作系統上,顯示不一樣,比如Win7 64bit是(&M)if ( -1 != wstrText.find(L"(")&& -1 != wstrText.find(L")") ) {// 新建文件夾按鈕hNewFloderButton = hChild;break;}}hChild = GetNextWindow(hChild, GW_HWNDNEXT);}char pszPath[MAX_PATH] = {0};LPITEMIDLIST pidl = (LPITEMIDLIST)(lParam);if ( NULL == pidl ) {return 0;}if (SHGetPathFromIDListA(pidl, pszPath)) {     HANDLE hFile = CreateFileA(pszPath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL  );if ( INVALID_HANDLE_VALUE == hFile )  {if ( NULL != hNewFloderButton ) {::EnableWindow(hNewFloderButton, FALSE);}::SendMessage(hwnd, BFFM_ENABLEOK, 0, 0 );}else {CloseHandle(hFile);hFile = NULL;if ( NULL != hNewFloderButton ) {::EnableWindow(hNewFloderButton, TRUE);}::SendMessage(hwnd, BFFM_ENABLEOK, 0, 1 );}}else {if ( NULL != hNewFloderButton ) {::EnableWindow(hNewFloderButton, FALSE);}::SendMessage(hwnd, BFFM_ENABLEOK, 0, 0 );}}else if ( uMsg == BFFM_VALIDATEFAILED ){return 1;}return 0; 
}

std::string ExportFodler()
{	char pszPath[MAX_PATH] = {0};  BROWSEINFOA bi;   bi.hwndOwner      = GetHwnd();  bi.pidlRoot       = NULL;  bi.pszDisplayName = NULL;   bi.lpszTitle      = "請選擇下載位置";   bi.ulFlags        = BIF_NEWDIALOGSTYLE;bi.lpfn           = BrowseCallbackProcSetting;bi.lParam         = 0;  bi.iImage         = 0;   LPITEMIDLIST pidl = SHBrowseForFolderA(&bi);  if ( NULL == pidl )  {return "";}if ( SHGetPathFromIDListA( pidl, pszPath ) )  {	strcat(pszPath,"\\");return std::string(pszPath);}return "";	
}

總結

以上是生活随笔為你收集整理的一个分析“文件夹”选择框实现方法的过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。