VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

有编程疑问吗?还请到提问专区发帖提问!
搜索
查看: 332|回复: 2

[原创] GetCurrentDirectory的问题及GetModuleFileName和PathRemoveFileSpec的解决办法!

[复制链接]
51_avatar_middle
online_admins Syc 发表于 2018-4-1 02:03:46 | 显示全部楼层 |阅读模式
这个问题属于老生常谈,今天遇到了PathRemoveFileSpec这个更为方便的函数,所以再给大家说一下加深下印象吧。
先来说说我们的需求,在平时的编程过程中,我们有很多时候都要获取EXE当前的路径,比如:C:\Test\123.exe 这个程序启动之后,我想要获取123.exe所在的目录,也就是 C:\Test 这个目录,目的是为了加载一些ini配置文件啦,或者加载一些DLL啦之类的。
找到的常用API函数就是 GetCurrentDirectory 函数了。

我们就说说 GetCurrentDirectory 这个函数,函数的原型声明如下:
  1. DWORD WINAPI GetCurrentDirectory(
  2. _In_ DWORD nBufferLength,
  3. _Out_ LPTSTR lpBuffer
  4. );
复制代码

该函数的作用就是获得进程的当前目录,大多数情况下都是正确的,例如,我新建一个对话框工程,之后添加一个按钮,在按钮的响应函数中添加如下代码:
  1. void CDialogTestDlg::OnBnClickedBtnTest()
  2. {
  3.     TCHAR szPath[MAX_PATH] = { 0 };
  4.     GetCurrentDirectory(MAX_PATH, szPath);
  5.     MessageBox(szPath, _T("Tip"), MB_OK);
  6. }
复制代码


其实就是调用 GetCurrentDirectory 获得程序的当前目录,之后调用 MessageBox 把这个路径提示出来。
GetCurrentDirectory的问题及GetModuleFileName和PathRemoveFileSpec的解决办法!
大家看到截图了吧,没问题的,调用 GetCurrentDirectory 成功的获取到了程序的当前目录。

但是,可但是,GetCurrentDirectory 是获得程序的当前目录,这个当前目录不一定是EXE所在的目录,下面给大家介绍两种异常的情况:
①、使用VS调试模式运行程序:
针对刚刚新建的对话框工程,我F5调试模式启动程序,再来看看结果:
GetCurrentDirectory的问题及GetModuleFileName和PathRemoveFileSpec的解决办法!
大家看到了吗?调用 GetCurrentDirectory 获取到的结果已经不是EXE所在的Debug目录了,而是 cpp、h 文件所在的目录,换句话说就是工程文件 vcxproj 所在的目录了,所以是以这个目录为程序的当前目录,不是我们想要的EXE所在的目录了,所以这时候如果你再去加载EXE所在目录下的 INI 配置文件,或者是 DLL 文件的话,肯定会找不到的;

②、使用 CFileDialog 进行打开文件的操作:该Bug只在XP系统下存在,后续系统该问题修复了!
void CDialogTestDlg::OnBnClickedBtnTest()
{
    CFileDialog m_FileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("All Files(*.*)|*.*||"), this);
    if (m_FileDlg.DoModal() == IDOK)
    {
        CString strPath = m_FileDlg.GetPathName();
    }

    TCHAR szPath[MAX_PATH] = { 0 };
    GetCurrentDirectory(MAX_PATH, szPath);
    MessageBox(szPath, _T("Tip"), MB_OK);
}

好了,既然有上面两种情况存在,那么我们再用 GetCurrentDirectory 来获取程序的当前路径可能就会有问题。那么怎么办呢?有没有其他函数替代?如果大家要现成的函数,一步到位的函数肯定是没有的。不过可以实现中转一下。
我们可以使用 GetModuleFileName 这个函数。该函数的声明如下:
  1. DWORD WINAPI GetModuleFileName(
  2.   _In_opt_ HMODULE hModule,
  3.   _Out_    LPTSTR  lpFilename,
  4.   _In_     DWORD   nSize
  5. );
复制代码

该函数是获得指定模块的完整路径,如果是想获取当前EXE模块的,那么第一个参数 hModule 直接传递 NULL 就可以了。下面我们使用一下:
  1. void CDialogTestDlg::OnBnClickedBtnTest()
  2. {
  3.     TCHAR szPath[MAX_PATH] = { 0 };
  4.     GetModuleFileName(NULL, szPath, MAX_PATH);
  5.     MessageBox(szPath, _T("Tip"), MB_OK);
  6. }
复制代码

提示的截图如下:
GetCurrentDirectory的问题及GetModuleFileName和PathRemoveFileSpec的解决办法!
不管是调试模式,还是之前调用过 CFileDialog 的打开/保存操作,GetModuleFileName 获得的结果都是指定模块的全路径信息。这里面也包含了该EXE的文件名字。所以,有了这个固定不变的路径之后,我们再自己手动调整下,把结尾的EXE文件名字去掉,这样就是我们想要的EXE所在的目录地址,方法比较就是基本的字符串操作,从字符串的末尾向前查找,直到遇到第一个 '\' 字符,就表明后面的都是EXE的文件名,直接去掉就可以,完整代码如下:
  1. BOOL GetCurrDirectory(LPTSTR lpBuffer, DWORD nBufferLength)
  2. {
  3.     if (!lpBuffer || nBufferLength <= 0) return FALSE;

  4.     TCHAR szPath[MAX_PATH] = { 0 };
  5.     DWORD dwRet = GetModuleFileName(NULL, szPath, MAX_PATH);
  6.     if (dwRet <= 0) return FALSE;

  7.     DWORD dwPathLen = _tcslen(szPath);
  8.     for (DWORD dwIdx = dwPathLen - 1; dwIdx >= 0; --dwIdx)
  9.     {
  10.         if (szPath[dwIdx] != '\\') continue;
  11.         szPath[dwIdx] = '\0';
  12.         break;
  13.     }

  14.     dwPathLen = _tcslen(szPath);
  15.     DWORD dwCopyLen = (nBufferLength - 1 <= dwPathLen) ? nBufferLength : dwPathLen;
  16.     _tcsncpy(lpBuffer, szPath, dwCopyLen);

  17.     return TRUE;
  18. }
复制代码


之前一直是用自己封装的这个 GetCurrDirectory 函数,感觉也还行。后来发现了 PathRemoveFileSpec API 函数。该函数的声明如下:
  1. BOOL PathRemoveFileSpec(
  2. _Inout_ LPTSTR pszPath
  3. );
复制代码

作用就是针对参数 szpPath 给定的一个文件路径,去掉结尾的文件名和 \ 字符。豁然开朗啊,所以我们的 GetCurrDirectory 函数就不用再自己去掉结尾的EXE文件名了,直接用这个函数就可以方便的搞定了。GetCurrDirectory 函数的代码修改如下:
  1. BOOL GetCurrDirectory(LPTSTR lpBuffer, DWORD nBufferLength)
  2. {
  3.     if (!lpBuffer || nBufferLength <= 0) return FALSE;

  4.     TCHAR szPath[MAX_PATH] = { 0 };
  5.     DWORD dwRet = GetModuleFileName(NULL, szPath, MAX_PATH);
  6.     if (dwRet <= 0) return FALSE;

  7.     PathRemoveFileSpec(szPath);

  8.     DWORD dwPathLen = _tcslen(szPath);
  9.     DWORD dwCopyLen = (nBufferLength - 1 <= dwPathLen) ? nBufferLength : dwPathLen;
  10.     _tcsncpy(lpBuffer, szPath, dwCopyLen);
  11.     lpBuffer[dwCopyLen] = '\0';

  12.     return TRUE;
  13. }
复制代码


好了,基本讲到这里就结束了。函数代码也都给大家提供现成的了,需要的直接拿去用吧!_tcsncpy 函数在不同版本的VS编译器中可能有的让用,有的不让用,这个也算是留给大家的小作业,自己回去实践修改吧。

评分

参与人数 1驿站币 +2 热心值 +2 收起 理由
38_avatar_small 2191265529 + 2 + 2 很给力!

查看全部评分





上一篇:VS2015最新版MSDN离线版安装与使用教程
下一篇:VS2013减小MFC对话框生成的EXE体积

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你已经在论坛发帖求助,并且从坛友或者管理的回复中解决了问题,请编辑帖子并把分类改成【已解决】

如何回报帮助你解决问题的坛友?可以给对方加【热心】【驿站币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

关闭

站长提醒上一条 /1 下一条

QQ
QQ在线咨询
联系电话
13591366679
手机扫一扫 关注本站精彩内容
wxqrcode

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号tongdun|网站地图wx_jqr

GMT+8, 2018-8-17 05:49

Powered by Discuz! X3.4

© 2009-2018 cctry.com

快速回复 返回顶部 返回列表