How to properly use GetModuleFileName?

73.9k views Asked by At

Following code:

#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();

}

Should show the full path where the program executes. But in VS 2012 I get the error:

uninitialized local variable 'buffer' used

What's wrong in code?

6

There are 6 answers

2
Mats Petersson On BEST ANSWER

You need to give it a buffer that can hold some characters;

 wchar_t buffer[MAX_PATH]; 

for example.

2
Praveer Kumar On

you may try this :

{
    TCHAR szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);
}
0
Volker On

I also ran across that issue with trusting char pszPath[_MAX_PATH] being enough for ::GetModuleFilename() to work properly (it didn't!). So I took the liberty to fiddle with Ivan's quite neat suggestion a bit and came up with the following code

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);
    }
}

which you can use in a number of ways, e. g.:

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

if you are OK with lambda expressions. If member variables are required as it is often the case in DLLs where you store away the HMODULE in DllMain(HMODULE hm, DWORD ul_reason_for_call, LPVOID), you must add the this pointer like below:

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

Of course, the "old fashioned way" also works as shown in this test case:

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);
}

and you could even use TCHAR for ANSI/UNICODE abstraction similar to this:

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

or #include <tstdlib.h> that I got from god-knows-where:

#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
}
2
Ivan Kolev On

This is a general problem with the Win32 API, functions return strings into a buffer of a limited size and you are never sure if your buffer was large enough to hold the whole string. Even MAX_PATH is not a good enough constant for paths these days, as kingsb mentioned.

I tend to use a general helper function for this purpose:

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 );
    }
}

Which for GetModuleFileName can be used like this:

extern HINSTANCE hInstance;

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

Or for LoadString like this:

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

0
Volker On

And, on a sidenote/addition to my answer below: Sometimes, you want to access a function size_t GetString(char* buf = NULL, size_t bufsize = 0); which will return the necessary buffer size if you call it without any parameters and - if you call it normally - writes up to bufsize characters into buf (or until the end of the string it wants to return, whatever comes first), returning the actual number of chars written. An implementation could look like this:

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);
    }
};

This typically happens in class factories that live in DLLs and don't want to exchange complex types like std::string because of memory management. To use the interface, you often end up with ugly code:

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

which sucks for obvious reasons, one being that you can't directly use this in stream insertion. After some procrastination and tearing my hair out, I came up with that almost pornographic solution, at least in terms of template and pointer-to-member-function usage:

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;
    }
};

which can then be used in the following way:

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.

Still a bit complicated but most of the heavy lifting is hidden behind the scenes.

2
kingsb On

VS properly points out that you are using an uninitialized buffer - buffer var is a pointer to WSTR, but it has not been initialized with the static buffer, neither has it been allocated. Also, you should remember that MAX_PATH is often not enough, especially on modern systems with long path names.

Since you are using C++, it would be a good practice to use it's features. I can suppose the following code:

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;

Note: before C++11, std::string and std::wstring were not guaranteed by the standard to have contiguous data and may not have been safe to use as a buffer. However, they are contiguous one in all major standard libraries.