Get the user's Desktop folder using Windows API?
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:
-
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; }
-
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; }
-
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 }
-
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; }
Related videos on Youtube
user780756
Updated on September 15, 2022Comments
-
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 aLPSTR
. 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:
- I want to add an else case where I return a string saying "ERROR"
- 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 over 10 yearsThanks, 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 over 10 yearsThis, 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 over 10 yearsSince you are using
LPWSTR
andwchar_t
explicitly, you need to useSHGetFolderPathW()
explicitly to match.SHGetFolderPath()
maps to eitherSHGetFolderPathA()
orSHGetFolderPathW()
depending on whetherUNICODE
is defined, just likeTCHAR
does forchar
andwchar_t
. My guess is that your project does not actually haveUNICODE
defined. -
user780756 over 10 yearsI added #define UNICODE #define _UNICODE and had to return a a LPCWSTR instead (export LPCWSTR desktop_directory()). This is correct, no?
-
Remy Lebeau over 10 years
UNICODE
should be enabled in the project settings, not in code. No, you do not need to returnLPCWSTR
, If you are going to rely onUNICODE
then returnLPTSTR
otherwiseLPWSTR
is fine. -
user780756 over 10 yearsI 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 over 10 yearsPlease update your question with the latest code you are having trouble with. UTF-8 is 8bit, so you need to use
LPSTR
(iechar*
) for it. -
ahmd0 over 7 years@RemyLebeau: Just a word of caution. I would stick with
CSIDL_DESKTOP
instead ofCSIDL_DESKTOPDIRECTORY
according to this article. Evidently it can make a difference in case of a redirectedroaming user profile
. -
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 ofCSIDL_DESKTOPDIRECTORY
instead, which does. If the code were working with PIDLs instead of paths, then yes,CSIDL_DESKTOP
would make more sense. -
ahmd0 over 7 years@RemyLebeau:
CSIDL_DESKTOP
returns a file system path alright. I agree that two constants are confusing:CSIDL_DESKTOP
andCSIDL_DESKTOPDIRECTORY
, but I will go with what a person who knows Windows internals recommends, i.e.CSIDL_DESKTOP
. -
Berkyjay almost 6 yearsThis will garble your whole Unicode string unless it consist of nothing but ASCII characters.