Get the user's Desktop folder using Windows API?

17,826

SHGetFolderPath() returns an HRESULT, where 0 is S_OK, but your code is expecting it to return a BOOL like SHGetSpecialFolderPath() does, where 0 is FALSE. So you need to fix that mistake in your code, as it is currently treating a success as if it were a failure instead.

With that said, you are returning a LPSTR from your function. That is a char*. But you are using TCHAR for your buffer. TCHAR maps to either char or wchar_t depending on whether UNICODE is defined or not. So you need to decide if you want to return a char* unconditionally, or if you want to return a TCHAR*, or both. It makes a big difference, eg:

#define _WIN32_WINNT    0x0500
#define _WIN32_IE       0x0500
#define CSIDL_MYMUSIC   0x000D
#define CSIDL_MYVIDEO   0x000E

#include "dll.h"
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>

export LPSTR desktop_directory()
{
    static char path[MAX_PATH+1];
    if (SHGetSpecialFolderPathA(HWND_DESKTOP, path, CSIDL_DESKTOP, FALSE))
        return path;
    else
        return "ERROR";
}

Versus:

#define _WIN32_WINNT    0x0500
#define _WIN32_IE       0x0500
#define CSIDL_MYMUSIC   0x000D
#define CSIDL_MYVIDEO   0x000E

#include "dll.h"
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>

export LPTSTR desktop_directory()
{
    static TCHAR path[MAX_PATH+1];
    if (SHGetSpecialFolderPath(HWND_DESKTOP, path, CSIDL_DESKTOP, FALSE))
        return path;
    else
        return TEXT("ERROR");
}

Versus:

#define _WIN32_WINNT    0x0500
#define _WIN32_IE       0x0500
#define CSIDL_MYMUSIC   0x000D
#define CSIDL_MYVIDEO   0x000E

#include "dll.h"
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>

export LPSTR desktop_directory_ansi()
{
    static char path[MAX_PATH+1];
    if (SHGetSpecialFolderPathA(HWND_DESKTOP, path, CSIDL_DESKTOP, FALSE))
        return path;
    else
        return "ERROR";
}

export LPWSTR desktop_directory_unicode()
{
    static wchar_t path[MAX_PATH+1];
    if (SHGetSpecialFolderPathW(HWND_DESKTOP, path, CSIDL_DESKTOP, FALSE))
        return path;
    else
        return L"ERROR";
}

Update: most Win32 API functions do not support UTF-8, so if you want the function to return a UTF-8 string, you will have to call the Unicode flavor of the functions, and then use WideCharToMultiByte() to convert the output to UTF-8. But then you have a problem - who allocates and frees the UTF-8 buffer? There are several different ways to handle that:

  1. use a thread-safe static buffer (but watch out for this gotcha). If you don't need to worry about multiple threads accessing the function, then drop the __declspec(thread) specifier:

    #define _WIN32_WINNT 0x0500
    #define _WIN32_IE 0x0500
    #define CSIDL_MYMUSIC 0x000D
    #define CSIDL_MYVIDEO 0x000E
    
    #include "dll.h"
    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    __declspec(thread) char desktop_dir_buffer[((MAX_PATH*4)+1];
    
    export LPCSTR desktop_directory()
    {
        wchar_t path[MAX_PATH+1] = {0};
    
        if (SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, path) != S_OK)
        {
            MessageBoxW(NULL, L"ERROR in SHGetFolderPathW", L"TEST", MB_OK);
            return NULL;
        }
    
        MessageBoxW(NULL, path, L"TEST", MB_OK);
    
        int buflen = WideCharToMultiByte(CP_UTF8, 0, path, lstrlenW(path), desktop_dir_buffer, MAX_PATH*4, NULL, NULL);
        if (buflen <= 0)
        {
            MessageBoxW(NULL, L"ERROR in WideCharToMultiByte", L"TEST", MB_OK);
            return NULL;
        }
    
        desktop_dir_buffer[buflen] = 0;
    
        return desktop_dir_buffer;
    }
    
  2. have the DLL dynamically allocate the buffer using its own memory manager and return it to the caller, and then require the caller to pass the buffer back to the DLL when done using it so it can be freed with the DLL's memory manager:

    #define _WIN32_WINNT 0x0500
    #define _WIN32_IE 0x0500
    #define CSIDL_MYMUSIC 0x000D
    #define CSIDL_MYVIDEO 0x000E
    
    #include "dll.h"
    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    export LPCSTR desktop_directory()
    {
        wchar_t path[MAX_PATH+1] = {0};
    
        if (SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, 0, path) != S_OK)
        {
            MessageBoxW(NULL, L"ERROR in SHGetFolderPathW", L"TEST", MB_OK);
            return NULL;
        }
    
        MessageBoxW(NULL, path, L"TEST", MB_OK);
    
        int pathlen = lstrlenW(path);
    
        int buflen = WideCharToMultiByte(CP_UTF8, 0, path, pathlen, NULL, 0, NULL, NULL);
        if (buflen <= 0)
        {
            MessageBoxW(NULL, L"ERROR in WideCharToMultiByte", L"TEST", MB_OK);
            return NULL;
        }
    
        char *buffer = new char[buflen+1];
        buflen = WideCharToMultiByte(CP_UTF8, 0, path, pathlen, buffer, buflen, NULL, NULL);
        if (buflen <= 0)
        {
            delete[] buffer;
            MessageBoxW(NULL, L"ERROR in WideCharToMultiByte", L"TEST", MB_OK);
            return NULL;
        }
        buffer[buflen] = 0;
    
        return buffer;
    }
    
    export void free_buffer(LPVOID buffer)
    {
        delete[] (char*) buffer;
    }
    
  3. have the DLL dynamically allocate the buffer using a Win32 API memory manager and return it to the caller, and then the caller can deallocate it using the same Win32 API memory manager without having to pass it back to the DLL to free it:

    #define _WIN32_WINNT 0x0500
    #define _WIN32_IE 0x0500
    #define CSIDL_MYMUSIC 0x000D
    #define CSIDL_MYVIDEO 0x000E
    
    #include "dll.h"
    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    export LPCSTR desktop_directory()
    {
        wchar_t path[MAX_PATH+1] = {0};
    
        if (SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, 0, path) != S_OK)
        {
            MessageBoxW(NULL, L"ERROR in SHGetFolderPathW", L"TEST", MB_OK);
            return NULL;
        }
    
        MessageBoxW(NULL, path, L"TEST", MB_OK);
    
        int pathlen = lstrlenW(path);
    
        int buflen = WideCharToMultiByte(CP_UTF8, 0, path, pathlen, NULL, 0, NULL, NULL);
        if (buflen <= 0)
        {
            MessageBoxW(NULL, L"ERROR in WideCharToMultiByte", L"TEST", MB_OK);
            return NULL;
        }
    
        char *buffer = (char*) LocalAlloc(LMEM_FIXED, buflen+1);
        if (!buffer)
        {
            MessageBoxW(NULL, L"ERROR in LocalAlloc", L"TEST", MB_OK);
            return NULL;
        }
    
        buflen = WideCharToMultiByte(CP_UTF8, 0, path, pathlen, buffer, buflen, NULL, NULL);
        if (buflen <= 0)
        {
            LocalFree(buffer);
            MessageBoxW(NULL, L"ERROR in WideCharToMultiByte", L"TEST", MB_OK);
            return NULL;
        }
        buffer[buflen] = 0;
    
        return buffer; // caller can use LocalFree() to free it
    }
    
  4. have the caller pass in its own buffer that the DLL simply fills in. That way, the caller can decide the best way to allocate and free it:

    #define _WIN32_WINNT 0x0500
    #define _WIN32_IE 0x0500
    #define CSIDL_MYMUSIC 0x000D
    #define CSIDL_MYVIDEO 0x000E
    
    #include "dll.h"
    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    // the caller can set buffer=NULL and buflen=0 to calculate the needed buffer size
    export int desktop_directory(LPSTR buffer, int buflen)
    {
        wchar_t path[MAX_PATH+1] = {0};
    
        if (SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, 0, path) != S_OK)
        {
            MessageBoxW(NULL, L"ERROR in SHGetFolderPathW", L"TEST", MB_OK);
            return -1;
        }
    
        MessageBoxW(NULL, path, L"TEST", MB_OK);
    
        int pathlen = lstrlenW(path);
    
        int len = WideCharToMultiByte(CP_UTF8, 0, path, pathlen, buffer, buflen, NULL, NULL);
        if (len <= 0)
        {
            MessageBoxW(NULL, L"ERROR in WideCharToMultiByte", L"TEST", MB_OK);
            return -1;
        }
    
        if (!buffer)
            ++len;
        else if (len < buflen)
            buffer[len] = 0;
    
        return len;
    }
    
Share:
17,826

Related videos on Youtube

user780756
Author by

user780756

Updated on September 15, 2022

Comments

  • user780756
    user780756 over 1 year

    I am trying to get the user's Desktop folder in a C++ application (via a DLL) by using SHGetSpecialFolderPath:

    #define _WIN32_WINNT    0x0500
    #define _WIN32_IE       0x0500
    #define CSIDL_MYMUSIC   0x000D
    #define CSIDL_MYVIDEO   0x000E
    
    #include "dll.h"
    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    TCHAR path[MAX_PATH];
    
    export LPSTR desktop_directory()
    {
    
        if (SHGetSpecialFolderPath(HWND_DESKTOP, path, CSIDL_DESKTOP, FALSE)) { 
            return path;
        }
    
    }
    

    First I want to return an else case. I return "ERROR" but the compiler warns me that is trying to convert a CHAR to a LPSTR. With that if there, it looks that the DLL might crash if it cannot get the directory for some reason.

    Also from the MSDN documentation, it says "[SHGetSpecialFolderPath is not supported. Instead, use ShGetFolderPath.]", then I navigate to that page and it says "ShGetFolderPath: Deprecated. Gets the path of a folder identified by a CSIDL value." What am I supposed to use instead?

    So:

    1. I want to add an else case where I return a string saying "ERROR"
    2. I want to know if I am using the correct non-deprecated API function that will work to the modern Windows OS as far back as Windows XP.

    EDIT

    Here is the updated code as requested,

    #ifndef UNICODE
    #define UNICODE
    #endif
    
    #ifndef _UNICODE
    #define _UNICODE
    #endif
    
    #define _WIN32_WINNT 0x0500
    #define _WIN32_IE 0x0500
    #define CSIDL_MYMUSIC 0x000D
    #define CSIDL_MYVIDEO 0x000E
    
    #include "dll.h"
    #include <windows.h>
    #include <shlobj.h>
    #include <stdio.h>
    
    export LPCWSTR desktop_directory()
    {
    
        static wchar_t path[MAX_PATH+1];
    
        if (SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, path)) {
            MessageBox(NULL, path, L"TEST", MB_OK); //test
            return path;
        } else {
            return L"ERROR";
        }
    
    }
    

    Compiling with MinGW using: g++ "src\dll\main.cpp" -D UNICODE -D _UNICODE -O3 -DNDEBUG -s -shared -o "output\main.dll"

    I need to pass the string from the DLL as UTF-8 using WideCharToMultiByte(CP_UTF8, ...), but I am not sure how to do that.

  • user780756
    user780756 over 10 years
    Thanks, the ANSI code gives a CHAR* conversion warning, but I'll use the unicode way instead. I'll try to use this with SHGetFolderPath()
  • user780756
    user780756 over 10 years
    This, export LPWSTR desktop_directory() { static wchar_t path[MAX_PATH+1]; if (SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, path)) { return path; } else { return L"ERROR"; } } complains that the 'path' is not of type LPTSTR for the SHGetFolderPath function. I could use a cast (LPTSTR)path on the function, but I am sure that would be the wrong way to do it? :c
  • Remy Lebeau
    Remy Lebeau over 10 years
    Since you are using LPWSTR and wchar_t explicitly, you need to use SHGetFolderPathW() explicitly to match. SHGetFolderPath() maps to either SHGetFolderPathA() or SHGetFolderPathW() depending on whether UNICODE is defined, just like TCHAR does for char and wchar_t. My guess is that your project does not actually have UNICODE defined.
  • user780756
    user780756 over 10 years
    I added #define UNICODE #define _UNICODE and had to return a a LPCWSTR instead (export LPCWSTR desktop_directory()). This is correct, no?
  • Remy Lebeau
    Remy Lebeau over 10 years
    UNICODE should be enabled in the project settings, not in code. No, you do not need to return LPCWSTR, If you are going to rely on UNICODE then return LPTSTR otherwise LPWSTR is fine.
  • user780756
    user780756 over 10 years
    I use MinGW so I have to define everything myself. I get warning: "deprecated string constant to tchar/wchar" if I use LPTSTR or LPWSTR. I need to pass a UTF8 string back from the dll though and someone told me to use WideCharToMultiByte() to pass it correctly. Have no idea how to use that function though.
  • Remy Lebeau
    Remy Lebeau over 10 years
    Please update your question with the latest code you are having trouble with. UTF-8 is 8bit, so you need to use LPSTR (ie char*) for it.
  • ahmd0
    ahmd0 over 7 years
    @RemyLebeau: Just a word of caution. I would stick with CSIDL_DESKTOP instead of CSIDL_DESKTOPDIRECTORY according to this article. Evidently it can make a difference in case of a redirected roaming user profile.
  • Remy Lebeau
    Remy Lebeau over 7 years
    @ahmd0 the code in question is returning a file system path. As I stated in my answer, CSIDL_DESKTOP is a virtual shell folder that does not have a file system path of its own. Thus the use of CSIDL_DESKTOPDIRECTORY instead, which does. If the code were working with PIDLs instead of paths, then yes, CSIDL_DESKTOP would make more sense.
  • ahmd0
    ahmd0 over 7 years
    @RemyLebeau: CSIDL_DESKTOP returns a file system path alright. I agree that two constants are confusing: CSIDL_DESKTOP and CSIDL_DESKTOPDIRECTORY, but I will go with what a person who knows Windows internals recommends, i.e. CSIDL_DESKTOP.
  • Berkyjay
    Berkyjay almost 6 years
    This will garble your whole Unicode string unless it consist of nothing but ASCII characters.