在软件开发中,我们如果存在“导入导出”的场景时,难免会用到“文件夹”选择框。之前一直没有太关注过这个的实现过程。最近在工作中遇到了一些问题,我做了一些研究。在此记录下研究的过程。(转载请指明出于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 "";
}

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

  1. 文件夹选择框 文件选择框

    最近在写一个上位机,需要选择文件和文件夹的功能,所以就查阅了一下,这里记录一下方便以后查阅. 文件夹选择框 & 文件选择框 文件夹选择框 文件选择框 工程文件 文件夹选择框 private v ...

  2. python利用pyside2创建文件夹选择框,文件选择框(无小窗口)

    python利用pyside2创建文件夹选择框,文件选择框(无小窗口) 文件夹选择框 文件选择框 文件多选框 之前利用win32ui创建文件选择窗口非常好用,但是想使用文件夹选择窗口发现行不通.利用t ...

  3. Python 文本对话框提示框 + 文件夹选择框 如何实现

    Python 文本对话框提示框: messagebox.showinfo("提示","你好,我是提示框") Python 文件夹选择框: foldr_patch ...

  4. vb中5种打开文件夹浏览框的方法总结(转)

    代码 众所周知,在vb中如果是打开某一个文件的话,非常简单,使用CommonDialog组件即可轻松完成,但是他只能选择文件,之后或许选取的文件路径,而如果想要浏览文件夹,就没这么方便了. 这里介绍3 ...

  5. 2016.2.17文件夹选择框及文件选择框

    string fpth = ""; OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Tit ...

  6. 文件夹选择对话框 JS实现的两种方案

    文件夹选择对话框 JS实现的两种方案 /**  * browseFolder.js  * 该文件定义了BrowseFolder()函数,它将提供一个文件夹选择对话框  * 以供用户实现对系统文件夹选择 ...

  7. 文件夹选择对话框 JS实现(转)

    /** * browseFolder.js * 该文件定义了BrowseFolder()函数,它将提供一个文件夹选择对话框 * 以供用户实现对系统文件夹选择的功能 * 文件夹选择对话框起始目录由 * ...

  8. C# WinForm 文件夹选择控件 folderBrowserDialog 的应用实例

    上期文章<使用 C# 修改文件创建时间>中有文件夹选择控件,但并没有提到如何使用,所以这次主要是针对这个控件讲下如何使用的一些细节.就两个动作,请看下图: 1.从工具箱中"双击& ...

  9. Bat调用/弹出文件或文件夹选择对话框

    弹出文件夹选择框 @echo off rem 弹出文件夹浏览选择对话框 set #=Any question&set @=WX&set $=Q&set/az=0x53b7e0b ...

最新文章

  1. indows上的android开发环境软件架构5
  2. python+oracle
  3. Educational Codeforces Round 44 (Rated for Div. 2)
  4. Spark记录-Scala基础语法
  5. python opencv SIFT,获取特征点的坐标位置
  6. java中的null类型---有关null的9件事
  7. 关于 UTXO 的思考
  8. redis 值字符串前面部分乱码_StringBoot 整合Redis解决存储乱码(通过StringRedisSerializer来进行序列化)...
  9. 天池 在线编程 最大子树(自底向上)
  10. Python+django网页设计入门(10):分页显示
  11. mysql去掉重复数据只保留一条,以及取分组后的一条数据
  12. matlab 滤波器_数字滤波器的MATLAB与FPGA实现
  13. 金融:收益利率计算器
  14. Kubernetes的Serializer解析
  15. WhatsApp营销与引流是怎么进行的?
  16. 正序,负序,零序分量的产生与计算
  17. 虚幻引擎4简介,UE4简介--这是一个强大的游戏开发引擎
  18. 客户体验和客户服务的区别
  19. 【动漫整理】好番推荐(含微量剧透)
  20. 400+汽车经销商怎么在一天完成算奖?

热门文章

  1. OpenCV中的二进制鲁棒独立基本特征——BRIEF
  2. opencvmediapipe 人脸检测+摄像头实时
  3. 基于onnx的人脸识别
  4. TensorFlow(3)张量与变量
  5. linux编译安装jpeg,Linux下JPEG库安装脚本(转)
  6. JAVA导出exls时报oom_如何实现导出百万条数据到EXCEL中不报OOM异常?
  7. Blender建筑可视化技能学习视频教程
  8. python pdb 基础调试
  9. 【洛谷习题】小A点菜
  10. python学习随笔(七)_函数