目录

附注

问题背景

实验测试

解决方案

方案对比

参考文档


附注

1、解释下问题含义,指在一个64位Windows系统上面(譬如Win7x64,Win10x64),如果一个32位的程序调用系统API打开文件对话框(譬如打开文件,保存文件)或者打开文件夹对话框,在指向系统盘下的Windows\System32目录时,会发现实际指向的是Windows\SysWOW64目录问题。

2、关于64位系统文件重定向问题,网上已经有很多文章进行了介绍,这里不再赘述(参考[1])。

3、测试验证环境:Windows10 x64 1909专业版。

4、打开/保存文件和打开文件夹,主要涉及API是GetOpenFileName和SHBrowseForFolder/SHGetPathFromIDList等(备注:打开文件夹会略微繁琐一点)。

问题背景

最近有客户反馈,使用我们提供的安全软件,在一些特殊场景(譬如信任文件),无法找到C:\Windows\System32下面一个指定的文件的文件(客户是想加白这个目录下面的一个文件)。收到反馈后,我们试验了下,发现真的有问题,不过初步判断应该就是x64系统上面的文件重定向问题(无法在文件对话框看到真正的c:\windows\system32下的子文件夹或者文件的原因是,被重定向到了c:\windows\syswow64下面,看到的都是这个目录下的文件和文件夹)。然而真正去解决的时候发现,bug比想象中难修复。

先准备这样一个环境:分别通过系统explorer手动建下面4个文件夹(也可以再在里面放一些具有标识作用的特殊文件):

C:\Windows\System32\00 我是真正的System32的子目录

C:\Windows\System32\01 大家都有的文件夹

C:\Windows\SysWOW64\00 我是SysWOWO64的子目录

C:\Windows\SysWOW64\01 大家都有的文件夹

(备注:标黄的部分是文件夹,如下图所示)

实验测试

1、用我们的安全软件,打开测试发现,定位到c:\Windows\System32目录发现展示果然实际上SysWOW64,此时是看不到真正的c:\Windows\System32下面的文件的。

2、测试了一些常见的IM软件和系统软件,发现IM软件基本上都有类似问题,但是对它们影响不大,因为IM软件打开保存文件主要是为了传文件,一般不会需要传一个system32下的文件,即使看不到也可以拷贝到桌面再传。至于系统软件,如果是64位的软件是没有这个问题的,但是32位的软件也一样有问题,但是基本上都不影响使用。

解决方案

问题比较明确,而且很显然就是前面的提到的文件重定向(这个特性是微软为了兼容32位程序的特意实现)问题,看起来似乎也有官方的解决方案,那就是关闭文件的重定向(参考[2] [3])。

1、在打开文件对话框前,直接调用Wow64DisableWow64FsRedirection先禁用文件重定向,关闭文件对话框之后再调用Wow64RevertWow64FsRedirection开启文件重定向,嗯,官方方案,稳定可靠,1个小时解决(备注,因这两个API只在vista之后才提供,因此如果程序还需要兼容xp,请通过导出函数的方式调用)!10分钟写完代码,测试,傻眼了,问题依旧。

对着API文档再撸了一遍,想起来这两个API是线程级的,也就是只影响当前线程(Disabling file system redirection affects only operations made by the current thread.),并不影响其它线程,这其实是微软的一个绝妙的设计,如果影响整个进程,那这两个API可能基本上就没什么用了。但是在这里,经过调试和观察发现,调用系统打开文件对话框之后,系统显然使用了其它的线程去枚举文件夹和文件,因此,在调用线程去禁用文件重定向自然是无用了。

2、难道微软就没有给打开文件对话框的API(以及类似相关API)提供可以直接禁用文件重定向的标识或者参数?查了下还真没有,网上也几乎没有相关资料,不知道是没人遇到到类似问题还是这个问题一般不严重(譬如IM软件,你把文件拷贝出来即可)?

3、做了一些初步的调试,发现微软在展示文件对话框时也有文件夹和文件的枚举动作,那么我们把这些API inline Hook起来,在微软调用前,先禁用重定向,待枚举完成再打开重定向,是否可行?看起来似乎可以,但是打开重定向的时机似乎不太好选择,而且目前只调试了一个系统,不清楚其它的系统是否也是类似的调用序列。而且这个方案也是有副作用的,因为一旦Hook生效之后,是基本上没法判断线程的归属的,可能另外一条线程也在枚举,但是它可能不需要关闭重定向。想想这个方案的可实践性太差,放弃。

4、我们自己实现一个文件对话框呢?逻辑和原理上完全可控,但是工作量太大,而且如果没有一个好的界面引擎支持,实现起来也是比较麻烦的,而且功能还一定完全满足,只是想了下感觉就可以放弃了(从有想法到放弃,估计10秒吧),当然还真的有自己实现的,只不过功能比较单薄(搜索,后缀过滤这些要实现都会比较麻烦)。

5、(备注:这里即将谈到的一个方案网上有人提到过,但是没有实现,这里简单实现了一下)前面提到官方提供的API(禁用/开启文件文件重定向)是线程级别的,在调用打开文件对话框API之后,我们是否有时机可以把一些新启动的线程的文件重定向全部关闭,待关闭文件对话框之后,再取消这个动作呢?思考了了一下,还真的有,DllMain有4个事件,其中一个是DLL_THREAD_ATTACH,在DLL成功加载之后,所有的新线程启动,都会在新线程环境,以这个参数调用一次所有DLL模块的DllMain函数,说干就干,这个DLL的代码很简单,提供两个导出函数,用于控制这个DLL里面的一个全局变量,该全局变量用于在新线程启动时判断是否需要禁用重定向。

调用侧在启动文件对话框(调用相关API前)时,先加载此DLL,然后调用导出函数DisableFsRedirection即可,在关闭文件对话框之后,调用导出函数RevertFsRedirection。

实验了一下,还是能解决问题的。但是这种方案也是有副作用的,还是无法区分线程谁是谁,一旦禁用了文件重定向,哪些线程受到了影响,无法确定。还有一个问题是,禁用了文件重定向的线程,再也没有机会去打开文件重定向了。一旦出现兼容问题,基本不可解。

如果是比较独立的小型EXE,这个方案是可以一试的。

bool g_bDisFsRedirect = false;BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved){switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:{KWow64FsRedrt oWow64FsRedrt;if (g_bDisFsRedirect && oWow64FsRedrt.Init()){oWow64FsRedrt.Close(); // 备注:无机会再打开重定向}break;}case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;}void __stdcall DisableFsRedirection(){g_bDisFsRedirect = true;return;}void __stdcall RevertFsRedirection(){g_bDisFsRedirect = false;return;}

6、有没有更靠谱一点,且没有副作用的方案呢?!前面实验的时候发现,纯64位程序天然是没有文件重定向问题的,从这个思路出发,似乎也是有一个解决方案的,把调用文件对话框的逻辑放到一个64位的独立进程里面,32位的程序根据不同的环境决定是否调用这个64位独立进程,当然可能需要一些机制(命令行参数?管道?)来传递必须的参数(窗口句柄,后缀过滤字符串,窗口名等等),同样需要一些机制来返回信息(譬如选中了哪个文件)给父进程。该方案的开发量适中,无副作用。

但是这个方案也不是完全无缺点的,它的缺点一个是多了一个进程,另外一个就是通用性略差(我的意思是同一个独立进程的情况),可能能适应大部分的场景,但是如果有需求需要部分改造文件对话框(微软有提供这个消息hook接口),那么就需要在这个独立进程里面写一些定制化的代码来支持这些特殊场景(也就是可能要改这个独立进程,无法通过简单的参数方式来解耦这个逻辑,可能通过插件可以解决,但是这样就把一个小问题搞得无比复杂了)。

简单通过传递子进程命令行和管道的方式实现了一下,基本上可用(当然这里有一些细节问题需要处理,但是基本上都有通用方案。譬如如何优雅的等待子进程,同时父进程又能处理消息,子进程异常后父进程窗口disable的处理,如何安全的读取子进程返回的管道数据,防止父进程ReadFile卡死等等)。

BOOL COpenFileDlg64::Open(HWND hwndOwner, LPCWSTR lpszCmdline, ATL::CAtlStringW& strPath){BOOL bRet = FALSE;STARTUPINFO si = {0};PROCESS_INFORMATION pi = {0};// 读写管道句柄HANDLE hStdOutRead = NULL;HANDLE hStdOutWrite = NULL;do{if (!lpszCmdline){break;}SECURITY_ATTRIBUTES sa = {0};sa.nLength = sizeof(sa);sa.bInheritHandle = TRUE; // 备注:重要sa.lpSecurityDescriptor = NULL;if  (!::CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0)){break;}si.cb = sizeof(STARTUPINFO); ::GetStartupInfo(&si);si.dwFlags  |= (STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK);si.hStdOutput = hStdOutWrite;si.hStdError  =  hStdOutWrite;CAtlString strCmdline(lpszCmdline);// 备注:重要,第5个参数设置为TRUEif (!::CreateProcessW(NULL,(LPTSTR)strCmdline.GetBuffer(),NULL,NULL,TRUE,0,NULL,NULL,&si,&pi)){strCmdline.ReleaseBuffer();break;}strCmdline.ReleaseBuffer();MSG msg = {0};BOOL bBreak = FALSE;DWORD dwWait = WAIT_FAILED;do{dwWait = ::MsgWaitForMultipleObjectsEx(1,&pi.hProcess,2000,QS_ALLEVENTS | QS_ALLINPUT,MWMO_INPUTAVAILABLE);switch (dwWait){case WAIT_OBJECT_0 + 1:{// 分发消息if (::PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)){::TranslateMessage(&msg);::DispatchMessage(&msg);}break;}case WAIT_OBJECT_0:{// 子进程已经退出DWORD dwRead = 0;DWORD dwAvailSize = 0;if (!::PeekNamedPipe(hStdOutRead, NULL, 0, &dwRead, &dwAvailSize, NULL) ||0 == dwAvailSize ||dwAvailSize > 4 * 1024){// 预览一个管道中的数据,用来判断管道中是否为空bBreak = TRUE;break;}// 申请内存ATL::CAutoVectorPtr<UCHAR> spVecPtr;if (!spVecPtr.Allocate(dwAvailSize + 2)){bBreak = TRUE;break;}memset((PUCHAR)spVecPtr, 0, dwAvailSize + 2);dwRead = 0;if (::ReadFile(hStdOutRead, (PVOID)spVecPtr, dwAvailSize, &dwRead, NULL) &&dwAvailSize == dwRead){std::wstring strOutInfo((WCHAR*)(PVOID)spVecPtr);Util::CCmdParser oParser(strOutInfo);oParser.Parse(L"##", L"==");int nErrorCode = -1;if (oParser.Get(L"errorcode", nErrorCode) && 0 == nErrorCode){std::wstring __strPath;oParser.Get(L"path", __strPath);strPath = __strPath.c_str();bRet = TRUE;}}break;}case WAIT_TIMEOUT:{continue;}default:{// 异常发生bBreak = TRUE;break;}} // End switch (dwWait)if (bBreak){// 结束wait循环break;}} while(dwWait != WAIT_OBJECT_0);} while (0);//// 集中释放资源//if (pi.hProcess){::CloseHandle(pi.hProcess);pi.hProcess = NULL;}if (pi.hThread){::CloseHandle(pi.hThread);pi.hThread = NULL;}if (hStdOutRead){::CloseHandle(hStdOutRead);hStdOutRead = NULL;}if (hStdOutWrite){::CloseHandle(hStdOutWrite);hStdOutWrite = NULL;}if (hwndOwner &&::IsWindow(hwndOwner) &&!::IsWindowEnabled(hwndOwner)){// 可能是子进程异常了//::EnableWindow(hwndOwner, TRUE);}return bRet;}

方案对比

方案

优点

缺点

备注

Hook方案

基本无,代码量可能不多

不可能因素多,且副作用较大

未实现过

UI实现对话框

完全可控

代码量极大,且特性可能不能完全满足

DLL捕获线程

代码量小

有副作用,不太可控

独立进程

代码量适中,基本无副作用

要实现特殊效果会比较麻烦,但是也有方案

☆推荐

代码下载:https://download.csdn.net/download/magictong/12337474

参考文档

[1] x64系统的判断和x64下文件和注册表访问的重定向(3) https://blog.csdn.net/magictong/article/details/5895482

[2] Wow64DisableWow64FsRedirection function https://docs.microsoft.com/en-us/windows/win32/api/wow64apiset/nf-wow64apiset-wow64disablewow64fsredirection

[3] Wow64RevertWow64FsRedirection function https://docs.microsoft.com/zh-cn/windows/win32/api/wow64apiset/nf-wow64apiset-wow64revertwow64fsredirection

Win32位程序(64位系统)文件对话框的文件重定向问题相关推荐

  1. php 不是有效的win32,%1不是有效的 win32应用程序(64位转换32位)

    在windows 2003 64位系统上安装完iis后,运行php文件提示%1不是有效的 win32应用程序,百度一下,终于找出问题,原来是windows2003 64位的操作系统和32位IIS的问题 ...

  2. (转)移植32位程序64位系统

    64个系统已经为32位的应用程序准备了运行32位程序的环境,这个环境就是WOW64的. WOW64 是Windows -32-on-Windows-64 的缩写.它为现有的 32 位应用程序提供了 3 ...

  3. 32位和64位系统对于程序员的影响

    win32,win64编程永恒;语言编程需要注意的64位和32机器的区别 一.数据类型特别是int相关的类型在不同位数机器的平台下长度不同.C99标准并不规定具体数据类型的长度大小,只规定级别.作下比 ...

  4. 32位程序调用64位dll_电脑系统怎样区分32位和64位

    一.32位和64位系统的区别: 32位系统和64位系统的区别有以下四点: 第一,CPU要求不同 第二,运算速度不同 第三,寻址能力不同 第四,软件兼容性不同 二.怎么看自己电脑系统是36位还是64位? ...

  5. 如何判断Unix系统的一个库文件是32位还是64位的

    如何判断Unix系统的一个库文件是32位还是64位的 某些时候,我们需要知道操作系统的位数,或者配置插件的时候需要知道主程序的位数(例如配置apache插件的时候需要知道apache的位数以便配置相应 ...

  6. c语言程序判断32位还是64位,c++ 判断是64位还是32位系统的实例

    1.IsWow64Process 确定指定进程是否运行在64位操作系统的32环境(Wow64)下. 语法 BOOL WINAPI IsWow64Process( __in HANDLE hProces ...

  7. 关于系统32位和64位的程序发布和部署(一)

    如果程序集文件只包含类型安全的托管代码,那么无论在32位版本的windows上,还是64位版本上的windows上,所写的代码都应该能正常工作.在这两种版本上的windowss上运行,源代码无需进行任 ...

  8. python 64位程序 打包用于32位系统_python分别打包出32位和64位应用程序

    由于我们分发的python应用可能运行在64位环境,也可能运行在32位环境,所以我们需要为同一套应用代码配置两套打包环境,怎么配置? 步骤如下 1,在电脑上分别下载安装32位和64位的python,安 ...

  9. Linux 系统如何查看文件是32位还是64位?

    在日常工作中,经常需要查看文件是32位还64位,下面一个命令可以查看: file filename

  10. 64 大小_32位和64位Windows系统差别在哪里

    如何确定32位和64位系统? 计算机体系结构是32位还是64位取决于计算机内部的处理器(CPU).目前,大多数计算机处理器属于这两个类别之一. 64位处理器比对应的32位处理器指数级更强大,因为它们可 ...

最新文章

  1. 高并发环境下如何优化Tomcat性能?看完我懂了!
  2. n皇后问题(回溯法-递归法和循环法,最小冲突法(较快解决10000级别问题))
  3. C++继承机制下的析构函数
  4. 使用远程工具连接提示**Host *** is not allowed to connect to this mysql server**拒绝连接错误
  5. python qt教程_Python - Python Qt 开发教程(1)
  6. bat 启动 不弹出对话框_CAD中转换出的PDF文件模糊要怎么办
  7. flexsim物流仿真案例_仓储物流设计-从设计方法到应用参数
  8. python海龟交易策略_Python的海龟交易法
  9. 苹果汽车项目团队添猛将?外媒称Apple Watch负责人已调入
  10. varnish 4.0 官方文档翻译14-Built in subroutines
  11. 【Ansible 文档】【译文】Ad-Hoc 命令介绍
  12. 【网络协议】IP协议、ARP协议、RARP协议
  13. ADO.NET五大对象详解(转)
  14. 计算机开机错误62,请问主板诊断卡错误代码62怎么办啊 ?
  15. Python调用OpenCV接口实现网络摄像头录像和抓拍图片
  16. 怎么制作升温曲线图_如何在EXcel做体温曲线图
  17. 昂科自动烧录器对英飞凌IR38164M系列芯片烧录,效果显著
  18. php坏处,cpu超频有什么坏处
  19. extern C C 调用c++
  20. 编码器输出:HTL、TTL

热门文章

  1. flash cache tier下放flush实验
  2. 深度学习篇之tensorflow(2) ---图像识别
  3. 人脸姿态估计预研(二)
  4. 汽车内外饰设计工程师是做什么的?有无发展前景-予菲汽车学习营分享
  5. SolrCloud原理
  6. CATIA嵌入Winform
  7. 记一次磁盘修复--error - read only 0 of 512 bytes
  8. oracle认证考试资格,Oracle认证考试报考条件(Oracle认证报名条件)
  9. foreign 磁盘阵列_联想服务器导入清除foreign阵列信息
  10. vmware+redhat9 摄像头驱动安装方法一