在软件开发中,我们如果存在“导入导出”的场景时,难免会用到“文件夹”选择框。之前一直没有太关注过这个的实现过程。最近在工作中遇到了一些问题,我做了一些研究。在此记录下研究的过程。(转载请指明出于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 "";
}
分享到:
相关推荐
### VC实现文件夹从一个目录下拷贝到另一个目录 #### 概述 在软件开发过程中,经常需要处理文件及文件夹的操作,如复制、移动等。本文将详细介绍如何使用Visual C++(简称VC)来实现文件夹从一个目录复制到另一个...
压缩包中的"目标目录选择框模块使用例子.e"文件很可能是一个可执行文件或者源代码示例,包含了完整的实现代码和运行环境。如果是一个可执行文件,我们可以通过运行来查看其实际效果,学习如何设计用户交互和反馈机制...
在这个文件夹对比工具中,VB的GUI组件被用来创建一个用户友好的界面,允许用户输入或选择要比较的文件夹。 其次,批处理功能意味着该工具可以一次处理多个文件夹对的比较,这对于需要大量文件夹对比的用户来说非常...
在本例中,当枚举到一个子目录时,我们再次调用枚举函数来处理该子目录,直到遍历完所有子目录。 3. **数据结构**:为了保存文件夹层次关系,可以使用树形数据结构,例如链表或者二叉树。在易语言中,我们可以...
总之,Android-ImageSelector是一个强大而灵活的图片选择框架,它能够帮助开发者轻松实现图片选择功能,同时提供高度自定义的选项,确保项目对图片选择的控制权。通过这个框架,你可以更专注于应用的核心业务,而...
1. **用户友好的界面**:它们通常有一个直观的界面,让用户可以通过简单的拖放或者输入路径来选择要监视的文件或文件夹。 2. **事件类型选择**:用户可以选择监听哪一类事件,如创建、修改、移动或删除。 3. **通知...
1. **`GetAllFiles`方法**:此方法首先通过`folderBrowserDialog`控件让用户选择一个文件夹,然后创建一个`DirectoryInfo`对象来表示这个文件夹。接着调用`GetFolder`方法来遍历这个文件夹,并将所有文件的信息收集...
为了实现递归,你可以创建一个辅助函数,接受文件夹路径作为参数,并在其内部调用上述步骤。 5. **清理与显示结果**:遍历完成后,记得调用`CFileFind`的`Close()`方法关闭查找。然后,你可以将结果展示给用户,...
界面通常会提供文件夹选择框,用户输入需要比较的文件夹路径。 2. 配置过滤:在工具的设置选项中,可以输入或选择需要过滤的文件扩展名,多个扩展名之间用分号隔开。 3. 开始比较:点击“开始”按钮,工具将开始...
在本文中,我们将深入探讨一个基于易语言编写的“文件夹整理助手”源码,帮助读者理解其背后的编程逻辑和实现方法。 首先,我们需要了解易语言的基本概念。易语言的设计理念是“让计算机懂人的语言”,因此它的语句...
标题中的“汇总指定文件夹内所有工作薄_ExcelVBA_”表明这是一个关于使用Excel的VBA(Visual Basic for Applications)编程技术来整合一个文件夹内所有Excel工作簿的项目。VBA是Microsoft Office应用程序中内置的一...
1. **目录结构**:在计算机中,文件和文件夹构成了一个层次结构,类似于一棵树。根目录位于顶部,下面可以有子目录,子目录下还可以有更深层的子目录,以此类推。遍历文件夹就是沿着这个目录树逐级向下查找。 2. **...
2. `GetOpenFileName`:如果使用的是带有文件夹选择对话框的“新建文件夹”功能,此函数可能用于获取用户输入的文件夹名称。 3. `ShellExecute`:可以用来执行创建文件夹的操作。 最后,压缩包内的“WINDOWS源码”...
结合以上知识点,你可以轻松地在VB程序中添加文件夹选择功能。`FolderBrowserDialog`控件使得这一过程变得简单而直观,无需复杂的代码就能实现用户友好的交互。 在提供的压缩包文件"选择文件夹窗口"中,可能包含了...
在JavaScript和jQuery的世界里,创建一个仿照Windows文件夹框选操作的插件是一个常见的需求,这能够增强用户的交互体验,特别是在处理大量可选择元素的场景下。本篇将详细介绍如何利用js/jq实现这样的功能,并提供一...
网站文件夹目录遍历是一种常见的网络爬虫技术,主要用于获取网站服务器上公开可访问的文件和目录结构。在Python编程语言中,特别是Python 3.6版本,我们可以利用其强大的HTTP请求库(如requests)和文件系统操作库...
这个实例源代码“搜索指定文件夹中子文件的数量.xlsm”就是一个很好的学习资源,它演示了如何使用VBA来实现这一功能。 首先,我们需要了解VBA(Visual Basic for Applications),它是Microsoft Office套件中的一种...
在日常使用计算机的过程中,我们经常会遇到一些棘手的问题,比如尝试删除一个空文件夹时,系统却提示“该项目已不在C用户XX桌面中”。这类问题虽然看似简单,但往往会耗费大量时间去解决。本文将详细探讨这一问题的...
`SHBrowseForFolder`函数是Windows Shell API中用于打开一个标准的文件夹选择对话框的函数。这个对话框允许用户在文件系统中导航并选择一个文件夹。函数接收一个`BROWSEINFO`结构作为参数,该结构包含了对话框的各种...