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

Openssl req命令
一、简介 req指令用来创建和处理PKCS#10格式的证书 二、语法 openssl req [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-out filename] [-text] [-pubkey] [-noout] [-verify] [-modulus] [-nodes] [-subject] [-passin arg] [-passout arg] [-key filename] [-key…
使用windbg抓取崩溃文件和分析的过程
在软件编程中,崩溃的场景比较常见的。且说微软技术再牛X,也是会出现崩溃的场景。网上有一段Win98当着比尔盖茨蓝屏的视频非常有意思。 (转载请指明出于breaksoftware的csdn博客)我们身边的很多软件都引入了dump生成和收集机制。但…
TF 2.1.0-rc2发布,2020年停止支持Python 2
作者 | 神经星星来源 | HyperAI超神经(ID:HyperAI)【导读】2020 年 1 月 1 日,Python 2 停止维护,正式退休。Python 3 全面登场的时刻,TensorFlow 也在悄悄改变。近日 TensorFlow 官方 GitHub 账号中,发布了…

重新认识笔记本锂电池的保养
重新认识笔记本锂电池的保养 对于笔记本电脑来说,电池可以说是一个比较重要的部件,它的效能直接关系到笔记本电脑在缺少电源的环境中的工作能力。而电池在笔记本电脑的众组件中又算是一个不折不扣的消耗品,因此涉及到笔记本电脑电池的保养和合…

nginx转发及后端服务器获取真实client的IP
针对nginx的模块介绍可以查阅wiki:http://wiki.nginx.org/Modules常用模块:HTTP CoreProxyRewriteUpstream 原理:squid,varnish以及nginx等,在做反向代理的时候,因为要代替客户端去访问服务器,所以…

AJAX的组成应用
表示层XHTMLCSS 动态显示和数据 DOM (文档对象模型)数据交互和操作 XML,XSLT 异步数据获取 XMLHttpRequest 绑定和处理数据 JavaScript XMLhttpRequest对象属性:Number readyState 4 表示完成Function onreadystatechange 回调函数string responseText XMLDocument responseXM…
打开,保存文件框的文本溢出排查
工作中遇到的这个问题还是很有意思的。其中嵌套了很多奇葩性的问题。 (转载请指明出于breaksoftware的csdn博客)我们来看下故事的发生过程,QA同学发现我们存在如下的bug 看到如此多的串,可以认为这个是典型的溢出问题。后来我咨询…
2020年,为什么说入坑AI是最好的时机?
2019年可以说是AI全面落地和商用的一年,产业智能化成为各个行业重点关注的发展方向,交通、工业、农业、医疗等主流行业无一例外。随着人工智能技术的进一步发展和落地,深度学习、数据挖掘、自动程序设计等领域也将在更多应用场景中得到实现。…

IIS 伪静态配置(安装ISAPI_Rewrite配置)
第一:首先到官方网站下载ISAPI_Rewrite 我的机子是32位的就下32位免费版的,链接地址如下: http://www.helicontech.com/download/isapi_rewrite/ISAPI_Rewrite3_0064_Lite.msi 可以选择不同版本:http://www.helicontech.com/downl…
Github标星24k,127篇经典论文下载,这份深度学习论文阅读路线图不容错过
作者 | Floodsung翻译 | 黄海广来源 | 机器学习初学者(ID:ai-start-com)【导读】如果你是深度学习领域的新手,那么你可能会遇到的第一个问题是“我应该从哪篇论文开始阅读?”本文就是一篇深度学习论文的阅读路线图!该…

c/c++面试
1. static在c,c中有什么不同点2. 堆和栈的区别3. 纯虚函数4. 指针和引用的区别5. 如果构造函数出错,如何处理?6. 对设计模式是否熟悉,用过哪些?7. c如何使用c中的函数,为什么?整理:1…

一种解决启动进程传递参数过长的方法
工作中,QA同学在测试我们程序的时候,发现在XP下,我们的A进程无法启动我们的B进程。而在Win7 64bit系统下功能正常。RD同学调试后,发现我们A进程中使用ShellExcute去启动了B进程(转载请指明出于breaksoftware的csdn博客…

Ubuntu“无法获得锁\加锁”解决方案
2019独角兽企业重金招聘Python工程师标准>>> 当你添加了源,更新源的时候,如果中途中断了更新,安装软件或者再次更新的时候就是出现如下提示, E: 无法获得锁 /var/lib/apt/lists/lock – open (11: 资源暂时不可用) E: …

一步一步学Silverlight 2系列(3):界面布局
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, Ironpython,对JSON、Web Service、WCF以及Sockets的支持等一系列新的特性。《一步一步学Silverlig…

一种准标准CSV格式的介绍和分析以及解析算法
CSV是一种古老的数据传输格式,它的全称是Comma-Separated Values(逗号分隔值)。出生在那个标准缺失的蛮荒年代,CSV的标准一直(到2005年)是NULL——世间存在着N种CSV格式,它们自成体系࿰…
新战场路在何方——详解360金融数据中台之旅
作者 |360金融架构总监黄建庭出品 | AI科技大本营(ID:rgznai100)本文为CSDN即将推出的《新战场:决胜中台》专刊的第 4 篇文章。自阿里巴巴引入中台概念后,市场对中台的关注度持续“高烧”不退。作为企业的基础平台,数据…

oracle中的exists 和not exists 用法详解
有两个简单例子,以说明 “exists”和“in”的效率问题 1) select * from T1 where exists(select 1 from T2 where T1.aT2.a) ; T1数据量小而T2数据量非常大时,T1<<T2 时,1) 的查询效率高。 2) select * from T1 where T1.a in (select…

现代内存编号解读(转)
现代SDRAM、DDR SDRAM、DDR2 SDRAM三种主流内存颗粒的编号一、DDR SDRAM:HYNIX DDR SDRAM颗粒编号:HY XX X XX XX X X X X X X X — XX X1 2 3 4 5 6 7 8 9 10 11 12 — 13 14整个DDR SDRAM颗粒的编号,一共是由14…
被追捧为“圣杯”的深度强化学习已走进死胡同
作者 | 朱仲光编译 | 夕颜出品 | AI科技大本营(ID:rgznai1100)【导读】近年来,深度强化学习成为一个被业界和学术界追捧的热门技术,社区甚至将它视为金光闪闪的通向 AGI 的圣杯,大多数人都看好它未来发展的巨大潜力。但…
一种清除windows通知区域“僵尸”图标的方案——问题分析
通知区域名称有趣的历史 假如说到windows通知区域,可能很多人还是不清楚它是什么。如果改称Tray区域,可能有人就懂了。如果再白话点,叫它“托盘”或者“系统托盘”,可能会有更多的人猜到它是windows什么部位。现在我们揭开…

Apache2.4+Tomcat7集群搭建
一、安装jdk、Tomcat、Apache1.安装jdk1.7cd /home/java/software #把软件下载到/home/java/software目录下,将应用安装到/home/java目录下。 wget http://download.oracle.com/otn/java/jdk/7u80-b15/jdk-7u80-linux-x64.tar.gz tar -zxvf jdk-7u80-linux-x64.tar…
一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案
XP下“僵尸”图标的解决方案 从《一种清除windows通知区域“僵尸”图标的方案——问题分析》(以后简称《问题分析》)一文中分析的通知区域结构可以看出,XP的通知区域结构是相对简单的。如果我们解决了XP下的问题,那么Win7上的问题…

《评人工智能如何走向新阶段》后记(再续12)
由AI科技大本营下载自视觉中国151. 新一代人工智能研究方向: (1)研究新一代人工智能基础理论(机理、模型和算法);(2)研发面向需求的共性技术(以神经网络和算法为核心、数据和硬件为基…

正则表达式测试工具 Regex Tester 的使用方法
2019独角兽企业重金招聘Python工程师标准>>> 正则表达式测试工具“RegexTester”,下载地址:http://www.oschina.net/p/regextester 一、关于本文 今天的工作中遇到了一些正则表达式,我需要检验它们是否正确,不过我对自…
一种清除windows通知区域“僵尸”图标的方案——Windows7系统解决方案
Windows7下“僵尸”图标的解决方案 从《一种清除windows通知区域“僵尸”图标的方案——问题分析》(以后简称《问题分析》)一文中分析的通知区域结构可以看出,Windows7的通知区域比XP通知区域多出了一个“临时”系统通知区域(转载…

《评人工智能如何走向新阶段》后记(再续13)
由AI科技大本营下载自视觉中国161. 引自美国科技媒体TNW记者对美欧企业主管与AI专家的访谈录摘要,谈到2020年AI的八大趋势: ①人工智能将使医疗保健更准确、成本更低; ②可解释性和信托及AI伦理将受到更多关注; ③在人工智能领…

在特定情况下的简单SSO实现方案
最近需要实现类似单点登录的功能。情况是这样的,最初在做网站A,做着做着,要做网站B了,要求与网站A完全分开作为两个应用,但用户数据要求与网站A保持一致,也要求用户在网站A登录后,转到网站B时不…

为创业者保驾护航 “无安全 不创业” 安全狗全国路演北京站
2019独角兽企业重金招聘Python工程师标准>>> 2015年上半年,网络安全问题毫无疑问已经成为了互联网行业关注的重点。在短短一年多的时间里,网络安全问题就从隐患转而呈现出爆发之势,即使是网易、支付宝、携程这样的互联网行业巨头也…
一种将快捷方式从开始菜单“常用应用”的中去除的方法
当我们安装一款软件的时候,这款软件的一些快捷方式可能被设置到开始菜单的“常用应用”区域。但是,如果是“卸载”快捷方式被“钉”到该区域,就会造成非常不好的体验。毕竟把“卸载”接口暴露得如此醒目,如同把该款软件的地狱大门…

ISA---不能访问网址或是多次刷新才能访问的解决方法一则
当你安装ISA2006在WINDOWS 2003 SERVER上,并打上SP2补订时。遇SNAT客户端不能访问WEB,但能PING通,能TELNET通,也能访问QQ或是MSN的问题时可以利用以下方法解决。同时,如果你遇到在此环境下,客户端访问外部网…