【问题标题】:How to properly use GetModuleFileName?如何正确使用 GetModuleFileName?
【发布时间】:2026-01-29 10:10:01
【问题描述】:

以下代码:

#include <iostream>
#include <Windows.h>

using namespace std;

int main ()
{   LPWSTR buffer; //or wchar_t * buffer;
    GetModuleFileName(NULL, buffer, MAX_PATH) ;
    cout<<buffer;
    cin.get();
    cin.get();

}

应该显示程序执行的完整路径。但是在 VS 2012 中我得到了错误:

使用了未初始化的局部变量“缓冲区”

代码有什么问题?

【问题讨论】:

    标签: c++ getmodulefilename


    【解决方案1】:

    你需要给它一个可以容纳一些字符的缓冲区;

     wchar_t buffer[MAX_PATH]; 
    

    例如。

    【讨论】:

    • 这是不正确的,GetModuleFileName 可以返回 ERROR_INSUFFICIENT_BUFFER 以获得长路径。
    • 关于OP,我认为是正确的。可能在某些情况下这还不够,并且会出错,但在大多数情况下,MAX_PATH 就足够了。请注意,OP 只使用了一个未初始化的指针,这肯定是错误的。
    【解决方案2】:

    VS 正确指出您正在使用未初始化的缓冲区 - buffer var 是指向 WSTR 的指针,但它没有用静态缓冲区初始化,也没有分配。此外,您应该记住 MAX_PATH 通常不够,尤其是在具有长路径名的现代系统上。

    由于您使用的是 C++,因此最好使用它的功能。我可以假设以下代码:

    vector<wchar_t> pathBuf; 
    DWORD copied = 0;
    do {
        pathBuf.resize(pathBuf.size()+MAX_PATH);
        copied = GetModuleFileName(0, &pathBuf.at(0), pathBuf.size());
    } while( copied >= pathBuf.size() );
    
    pathBuf.resize(copied);
    
    wstring path(pathBuf.begin(),pathBuf.end());
    
    cout << path;
    

    不要直接使用 wstring 作为缓冲区:它没有被定义为在每个实现中都有一个连续的缓冲区(但通常是)

    【讨论】:

    • 这个答案*.com/a/28024825 说标准明确要求 basic_string 对象的内部缓冲区是连续的。其中包括 wstring,它是一个 basic_string 对象。
    • 您可以使用pathBuf.data() 而不是&amp;pathBuf.at(0) 来获取指向底层数据数组的指针。 en.cppreference.com/w/cpp/container/vector/data
    【解决方案3】:

    这是 Win32 API 的一个普遍问题,函数将字符串返回到有限大小的缓冲区中,您永远无法确定缓冲区是否足够大以容纳整个字符串。正如 kingsb 所提到的,即使是 MAX_PATH 也不足以成为如今的路径常量。

    我倾向于为此使用通用辅助函数:

    template <typename TChar, typename TStringGetterFunc>
    std::basic_string<TChar> GetStringFromWindowsApi( TStringGetterFunc stringGetter, int initialSize = 0 )
    {
        if( initialSize <= 0 )
        {
            initialSize = MAX_PATH;
        }
    
        std::basic_string<TChar> result( initialSize, 0 );
        for(;;)
        {
            auto length = stringGetter( &result[0], result.length() );
            if( length == 0 )
            {
                return std::basic_string<TChar>();
            }
    
            if( length < result.length() - 1 )
            {
                result.resize( length );
                result.shrink_to_fit();
                return result;
            }
    
            result.resize( result.length() * 2 );
        }
    }
    

    GetModuleFileName 可以这样使用:

    extern HINSTANCE hInstance;
    
    auto moduleName = GetStringFromWindowsApi<TCHAR>( []( TCHAR* buffer, int size )
    {
        return GetModuleFileName( hInstance, buffer, size );
    } );
    

    或者像这样的LoadString:

    std::basic_string<TCHAR> LoadResourceString( int id )
    {
        return GetStringFromWindowsApi<TCHAR>( [id]( TCHAR* buffer, int size )
        {
            return LoadString( hInstance, id, buffer, size );
        } );
    }
    
    

    【讨论】:

    • 很酷的辅助功能。最后一个示例不应该使用LoadStringTCHAR,还是使用charLoadStringA
    • 谢谢,错过了从实际代码复制/粘贴后,会更正它。
    【解决方案4】:

    并且,在我下面的答案的旁注/补充中:有时,您想访问一个函数 size_t GetString(char* buf = NULL, size_t bufsize = 0); 如果你在没有任何参数的情况下调用它,它将返回必要的缓冲区大小 - 如果你正常调用它 - 将最多 bufsize 个字符写入 buf (或直到它想要返回的字符串的末尾,无论先出现什么),返回实际写入的字符数。实现可能如下所示:

    class GetterClass
    {
    private:
        size_t String2Buffer(const std::string& string, char* const pBuffer = NULL, size_t size = 0)
        {
            size_t s, length = string.length() + 1;
            if (!pBuffer) return length;
            s = std::min<>(length, size);
            memcpy(pBuffer, string.c_str(), s);
            return s;
        }
    public:
        size_t GetterFunc(char* p, size_t len)
        {
            const static std::string s("Hello World!");
            return String2Buffer(s, p, len);
        }
    };
    

    这通常发生在类工厂中,这些类工厂存在于 DLL 中,并且由于内存管理而不想交换 std::string 等复杂类型。要使用该界面,您通常会得到丑陋的代码:

    GetterClass g;
    GetterClass* pg = &g;
    std::string s(pg->GetterFunc() - 1, '\0');
    pg->GetterFunc(&s[0], s.size());
    

    这很糟糕,原因很明显,一个是你不能直接在流插入中使用它。经过一些拖延和撕毁我的头发,我想出了那个几乎色情的解决方案,至少在模板和指向成员函数的使用方面:

    template <typename tClass>
    struct TypeHelper
    {
        typedef size_t(tClass::* MyFunc)(char*, size_t);
        static std::string Get(MyFunc fn, tClass& c)
        {
            size_t len = (c.*fn)(NULL, 0);
            std::string s(len - 1, '\0');
            size_t act = (c.*fn)(&s[0], len - 1);
            assert(act == s.size());
            return s;
        }
    };
    

    然后可以通过以下方式使用:

    GetterClass Foo;
    GetterClass* pFoo = &Foo;
    std::string s1 = TypeHelper<GetterClass>::Get(&GetterClass::GetterFunc, Foo); // Class version.
    std::string s2 = TypeHelper<GetterClass>::Get(&GetterClass::GetterFunc, *pFoo); // Pointer-to-Instance version.
    

    仍然有点复杂,但大部分繁重的工作都隐藏在幕后。

    【讨论】:

      【解决方案5】:

      我也遇到了这个问题,相信char pszPath[_MAX_PATH] 足以让::GetModuleFilename() 正常工作(它没有!)。所以我冒昧地把 Ivan 相当巧妙的建议摆弄了一下,并想出了以下代码

      template <typename TChar, typename TStringGetterFunc>
      std::basic_string<TChar> GetStringFromWindowsApi(TStringGetterFunc stringGetter, typename std::basic_string<TChar>::size_type initialSize = _MAX_PATH)
      {
          std::basic_string<TChar> sResult(initialSize, 0);
          while(true)
          {
              auto len = stringGetter(&sResult[0], sResult.length());
              if (len == 0) return std::basic_string<TChar>();
      
              if (len < sResult.length() - 1)
              {
                  sResult.resize(len);
                  sResult.shrink_to_fit();
                  return sResult;
              }
      
              sResult.resize(sResult.length() * 2);
          }
      }
      

      您可以通过多种方式使用它,例如。 g.:

      std::string sModuleFileName = GetStringFromWindowsApi<char>([](char* buffer, int size)
      {
          return ::GetModuleFileNameA(NULL, buffer, size);
      });
      

      如果你对 lambda 表达式没问题。如果需要成员变量(在 DLL 中通常是这种情况,您将 HMODULE 存储在 DllMain(HMODULE hm, DWORD ul_reason_for_call, LPVOID) 中),则必须添加 this 指针,如下所示:

      std::string sModuleFileName = GetStringFromWindowsApi<char>([this](char* buffer, int size)
      {
          return ::GetModuleFileNameA(m_MyModuleHandleHere, buffer, size);
      });
      

      当然,“老式方式”也可以像这个测试用例一样工作:

      typedef int (PGetterFunc)(char*, int);
      int MyGetterFunc(char* pszBuf, int len)
      {
          static const char psz[] = "**All your base are belong to us!**";
          if (len < strlen(psz) + 1)
          {
              strncpy(pszBuf, psz, len - 1);
              return len;
          }
          strcpy(pszBuf, psz);
          return static_cast<int>(strlen(psz));
      }
      
      void foo(void)
      {
          std::string s = GetStringFromWindowsApi<char, PGetterFunc>(MyGetterFunc, _MAX_PATH);
      }
      

      您甚至可以将 TCHAR 用于 ANSI/UNICODE 抽象,类似于:

      std::basic_string<TCHAR> sModuleFileName = GetStringFromWindowsApi<TCHAR>([](TCHAR* buffer, int size)
      {
          return ::GetModuleFileName(NULL, buffer, size);
      });
      

      #include &lt;tstdlib.h&gt;,我从上帝知道的地方得到的:

      #include <string>
      #include <sstream>
      #include <iostream>
      #include <fstream>
      #include <stdlib.h>
      #include <assert.h>
      
      #ifdef _WIN32
      #include <tchar.h> // For _T() macro!
      #else // Non Windows Platform!
      #ifdef _UNICODE
      typedef wchar_t TCHAR;
      #ifndef _T
      #define _T(s) L##s
      #endif
      #ifndef _TSTR
      #define _TSTR(s) L##s
      #endif
      #else
      typedef wchar_t TCHAR;
      #ifndef _T
      #define _T(s) s
      #endif
      #ifndef _TSTR
      #define _TSTR(s) s
      #endif
      #endif
      #endif
      
      /// <summary>
      /// The tstd namespace contains the STL equivalents to TCHAR as defined in <tchar.h> to allow
      /// all the Unicode magic happen with strings and streams of the STL.
      /// Just use tstd::tstring instead of std::string etc. and the correct types will automatically be selected
      /// depending on the _UNICODE preprocessor flag set or not.
      /// E. g.
      /// tstd::tstring will resolve to std::string if _UNICODE is NOT defined and
      /// tstd::tstring will resolve to std::wstring _UNICODE IS defined.
      /// </summary>
      namespace tstd
      {
      #ifdef _UNICODE
          // Set the wide character versions.
          typedef std::wstring tstring;
          typedef std::wostream tostream;
          typedef std::wistream tistream;
          typedef std::wiostream tiostream;
          typedef std::wistringstream tistringstream;
          typedef std::wostringstream tostringstream;
          typedef std::wstringstream tstringstream;
          typedef std::wifstream tifstream;
          typedef std::wofstream tofstream;
          typedef std::wfstream tfstream;
          typedef std::wfilebuf tfilebuf;
          typedef std::wios tios;
          typedef std::wstreambuf tstreambuf;
          typedef std::wstreampos tstreampos;
          typedef std::wstringbuf tstringbuf;
      
          // declare an unnamed namespace as a superior alternative to the use of global static variable declarations.
          namespace
          {
              tostream& tcout = std::wcout;
              tostream& tcerr = std::wcerr;
              tostream& tclog = std::wclog;
              tistream& tcin = std::wcin;
      
              /// <summary>
              /// Unicode implementation for std::endl.
              /// </summary>
              /// <param name="output">Output character stream.</param>
              /// <returns>Output character stream.</returns>
              std::wostream& tendl(std::wostream& output)
              {
                  output << std::endl;
                  return output;
              }
      
              /// <summary>
              /// wstr to tstr conversion for Unicode. Nothing to do here but copying things.
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from std::wstring to tstd::tstring.</returns>
              tstring wstr_to_tstr(const std::wstring& arg)
              {
                  return arg;
              }
      
              /// <summary>
              /// str to tstr conversion for Unicode. Internally calls mbstowcs() for conversion..
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from std::string to tstd::tstring.</returns>
              tstring str_to_tstr(const std::string& arg)
              {
                  tstring res(arg.length() + 1, L'\0'); // +1 because wcstombs_s() always wants to write a terminating null character.
                  size_t converted;
                  mbstowcs_s(&converted, const_cast<wchar_t*>(res.data()), res.length(), arg.c_str(), arg.length()); // Using the safer version of mbstowcs() here even though res is always defined adequately.
                  assert(converted - 1 == arg.length()); // Sanity test.
                  res.resize(res.size() - 1); // Remove '\0'.
                  return res;
              }
      
              /// <summary>
              /// tstr to wstr conversion for Unicode. Nothing to do here but copying things.
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from tstd::tstring to std::wstring.</returns>
              std::wstring tstr_to_wstr(const tstring& arg)
              {
                  return arg;
              }
      
              /// <summary>
              /// tstr to str conversion for Unicode. Internally calls wcstombs() for conversion.
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from tstd::tstring to std::string.</returns>
              std::string tstr_to_str(const tstring& arg)
              {
                  std::string res(arg.length() + 1, '\0'); // +1 because wcstombs_s() always wants to write a terminating null character.
                  size_t converted;
                  wcstombs_s(&converted, const_cast<char*>(res.data()), res.length(), arg.c_str(), arg.length()); // Using the safer version of wcstombs() here even though res is always defined adequately.
                  assert(converted - 1 == arg.length()); // Sanity test.
                  res.resize(res.size() - 1); // Remove '\0'.
                  return res;
              }
          }
      
      #else
      
          // Set the multibyte versions.
          typedef std::string tstring;
          typedef std::ostream tostream;
          typedef std::istream tistream;
          typedef std::iostream tiostream;
          typedef std::istringstream tistringstream;
          typedef std::ostringstream tostringstream;
          typedef std::stringstream tstringstream;
          typedef std::ifstream tifstream;
          typedef std::ofstream tofstream;
          typedef std::fstream tfstream;
          typedef std::filebuf tfilebuf;
          typedef std::ios tios;
          typedef std::streambuf tstreambuf;
          typedef std::streampos tstreampos;
          typedef std::stringbuf tstringbuf;
      
          // declare an unnamed namespace as a superior alternative to the use of global static variable declarations.
          namespace
          {
              tostream& tcout = std::cout;
              tostream& tcerr = std::cerr;
              tostream& tclog = std::clog;
              tistream& tcin = std::cin;
      
              /// <summary>
              /// Multibyte implementation for std::endl.
              /// </summary>
              /// <param name="output">Output character stream.</param>
              /// <returns>Output character stream.</returns>
              std::ostream& tendl(std::ostream& output)
              {
                  output << std::endl;
                  return output;
              }
      
              /// <summary>
              /// wstr to tstr conversion for multibyte. Internally calls wcstombs() for conversion.
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from std::wstring to tstd::tstring.</returns>
              tstring wstr_to_tstr(const std::wstring& arg)
              {
                  tstring res(arg.length()+1, '\0'); // +1 because wcstombs_s() always wants to write a terminating null character.
                  size_t converted;
                  wcstombs_s(&converted, const_cast<char*>(res.data()), res.length(), arg.c_str(), arg.length()); // Using the safer version of wcstombs() here even though res is always defined adequately.
                  assert(converted-1 == arg.length()); // Sanity test.
                  res.resize(res.size() - 1); // Remove '\0'.
                  return res;
              }
      
              /// <summary>
              /// str to tstr conversion for multibyte. Nothing to do here but copying things.
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from std::string to tstd::tstring.</returns>
              tstring str_to_tstr(const std::string& arg)
              {
                  return arg;
              }
      
              /// <summary>
              /// tstr to wstr conversion for multibyte. Internally calls mbstowcs() for conversion..
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from tstd::tstring to std::wstring.</returns>
              std::wstring tstr_to_wstr(const tstring& arg)
              {
                  std::wstring res(arg.length()+1, L'\0'); // +1 because wcstombs_s() always wants to write a terminating null character.
                  size_t converted;
                  mbstowcs_s(&converted, const_cast<wchar_t*>(res.data()), res.length(), arg.c_str(), arg.length());
                  assert(converted-1 == arg.length()); // Sanity test.
                  res.resize(res.size() - 1); // Remove '\0'.
                  return res;
              }
      
              /// <summary>
              /// tstr to str conversion for multibyte. Nothing to do here but copying things.
              /// </summary>
              /// <param name="arg">Input string.</param>
              /// <returns>Output string conversted from tstd::tstring to std::string.</returns>
              std::string tstr_to_str(const tstring& arg)
              {
                  return arg;
              }
          }
      
      #endif
      }
      

      【讨论】: