Getting user temporary folder path in Windows - c++

How I can get the user's temp folder path in C++? My program has to run on Windows Vista and XP and they have different temp paths. How I can get it without losing compatibility?

Is there a reason you can't use the Win32 GetTempPath API?
http://msdn.microsoft.com/en-us/library/aa364992(VS.85).aspx
This API is available starting with W2K and hence will be available on all of your listed targets.

Since C++ 17 you can use a cross-platform function:
std::filesystem::temp_directory_path()
https://en.cppreference.com/w/cpp/filesystem/temp_directory_path

The GetTempPath function retrieves the path of the directory designated for temporary files. This function supersedes the GetTempDrive function.
DWORD GetTempPath(
DWORD nBufferLength, // size, in characters, of the buffer
LPTSTR lpBuffer // address of buffer for temp. path
);
Parameters
nBufferLength
Specifies the size, in characters, of the string buffer identified by lpBuffer.
lpBuffer
Points to a string buffer that receives the null-terminated string specifying the temporary file path.
Return Values
If the function succeeds, the return value is the length, in characters, of the string copied to lpBuffer, not including the terminating null character. If the return value is greater than nBufferLength, the return value is the size of the buffer required to hold the path.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Remarks
The GetTempPath function gets the temporary file path as follows:
The path specified by the TMP environment variable.
The path specified by the TEMP environment variable, if TMP is not defined.
The current directory, if both TMP and TEMP are not defined.

In Windows 10, this can be tricky because the value of the Temporary Path depends not only what it's set to by default, but also what kind of app you're using. So it depends what specifically you need.
[Common Area] TEMP in User's Local App Data
#include <Windows.h>
#include <Shlobj.h>
#include <Shlobj_core.h>
#include <string_view>
// ...
static void GetUserLocalTempPath(std::wstring& input_parameter) {
static constexpr std::wstring_view temp_label = L"\\Temp\\";
HWND folder_handle = { 0 };
WCHAR temp_path[MAX_PATH];
auto get_folder = SHGetFolderPath(
folder_handle, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_DEFAULT, temp_path
);
if (get_folder == S_OK) {
input_parameter = static_cast<const wchar_t*>(temp_path);
input_parameter.append(temp_label);
CloseHandle(folder_handle);
}
}
GetUserLocalTempPath will likely return the full name instead of the short name.
Also, if whatever is running it is doing it as as SYSTEM instead of a logged in user, instead of it returning %USERPROFILE%\AppData\Local\Temp, it will return something more like, C:\Windows\System32\config\systemprofile\AppData\Local\Temp
Temp for whatever the TEMP environment variable is
#include <Windows.h>
// ...
static void GetEnvTempPath(std::wstring& input_parameter) {
wchar_t * env_var_buffer = nullptr;
std::size_t size = 0;
if ( _wdupenv_s(&env_var_buffer, &size, L"TEMP") == 0 &&
env_var_buffer != nullptr) {
input_parameter = static_cast<const wchar_t*>(env_var_buffer);
}
}
[Robust] Temp for whatever is accessible by your app (C++17)
#include <filesystem>
// ...
auto temp_path = std::filesystem::temp_directory_path().wstring();
temp_directory_path will likely return the short name instead of the full name.
You're probably going to get the most use out of the first and last functions depending on your needs. If you're dealing with AppContainer apps, go for the last one provided by <filesystem>. It should return something like,
C:\Users\user name\AppData\Local\Packages\{APP's GUID}\AC\Temp

Use GetTempPath() to retrieve the path of the directory designated for temporary files.
wstring TempPath;
wchar_t wcharPath[MAX_PATH];
if (GetTempPathW(MAX_PATH, wcharPath))
TempPath = wcharPath;

#include <iostream>
#include <string>
int main(int argc, char* argv[]){
std::cout << getenv("TEMP") << std::endl;
return 0;
}

Function GetTempPath will return a path with a short name,eg: C:\Users\WDKREM~1\AppData\Local\Temp\.
To get a full temp path name,use GetLongPathName subsequently.

GetTempPath isn't going to work on Vista unless the users have administrative access. I'm running into that problem right now with one of my apps.

As VictorV pointed out, GetTempPath returns a collapsed path. You'll need to use both the GetTempPath and GetLongPathName macros to get the fully expanded path.
std::vector<TCHAR> collapsed_path;
TCHAR copied = MAX_PATH;
while ( true )
{
collapsed_path.resize( copied );
copied = GetTempPath( collapsed_path.size( ), collapsed_path.data( ) );
if ( copied == 0 )
throw std::exception( "An error occurred while creating temporary path" );
else if ( copied < collapsed_path.size( ) ) break;
}
std::vector<TCHAR> full_path;
copied = MAX_PATH;
while ( true )
{
full_path.resize( copied );
copied = GetLongPathName( collapsed_path.data( ), full_path.data( ), full_path.size( ) );
if ( copied == 0 )
throw std::exception( "An error occurred while creating temporary path" );
else if ( copied < full_path.size( ) ) break;
}
std::string path( std::begin( full_path ), std::end( full_path ) );

Related

Append to registry without expanding variables

I'll just start off by saying that I'm by no means an expert in C++, so any pointers/tips are greatly appreciated.
I'm having some difficulties reading and writing from registry, while keeping variables, i.e. not expanding them.
I'm trying to append my executable path to the PATH environment variable (permanently), but I'm running into all sorts of problems.
I have a long PATH variable that makes it impossible to edit without using a program or regedit, so I opted to create an "OldPath" variable with my current PATH variable, and change my PATH variable to %OldPath%. This has worked great, but now when I try to write to it with C++, %OldPath% gets expanded into the old path variable and as a result, the variable gets truncated.
I tried first with normal strings, but I ended up with what looked like Chinese symbols in my PATH variable, so I changed it to wstring. Now I get normal strings, but the string gets truncated at 1172 characters.
My desired end result is that PATH is set to %OldPath;<current_path>
get_path_env()
inline std::wstring get_path_env()
{
wchar_t* buf = nullptr;
size_t sz = 0;
if (_wdupenv_s(&buf, &sz, L"PATH") == 0 && buf != nullptr)
{
std::wstring path_env = buf;
free(buf);
return path_env;
}
return L"";
}
set_permanent_environment_variable()
inline bool set_permanent_environment_variable()
{
const std::wstring path_env = get_path_env();
if (path_env == L"")
{
return false;
}
std::wstringstream wss;
wss << path_env;
if (path_env.back() != ';')
{
wss << L';';
}
wss << std::filesystem::current_path().wstring() << L'\0';
const std::wstring temp_data = wss.str();
HKEY h_key;
const auto key_path = TEXT(R"(System\CurrentControlSet\Control\Session Manager\Environment)");
if (const auto l_open_status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_path, 0, KEY_ALL_ACCESS, &h_key); l_open_status == ERROR_SUCCESS)
{
const auto data = temp_data.c_str();
const DWORD data_size = static_cast<DWORD>(lstrlenW(data) + 1);
// ReSharper disable once CppCStyleCast
const auto l_set_status = RegSetValueExW(h_key, L"PATH", 0, REG_EXPAND_SZ, (LPBYTE)data, data_size);
RegCloseKey(h_key);
if (l_set_status == ERROR_SUCCESS)
{
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>("Environment"), SMTO_BLOCK, 100, nullptr);
return true;
}
}
return false;
}
In other words, I want to find the equivalent of the following in C#:
var assemblyPath = Directory.GetParent(Assembly.GetEntryAssembly()!.Location).FullName;
var pathVariable = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("PATH", $"{pathVariable};{assemblyPath}", EnvironmentVariableTarget.Machine);
EDIT: I actually haven't tested if that code expands the value or not, but I want to do as the C# code states and if possible, not expand the variables in the path variable.
You are trying to change the PATH setting in the registry. So one would expect that you would get the current PATH setting from the registry, change it, and set the new PATH setting in the registry.
But you are not getting the PATH setting from the registry. You are getting the PATH variable from the environment instead. Why is that? The environment is controlled by the setting in the registry, but it's not that setting. In particular, you noticed that the environment variables set in the registry get expanded before they actually go into the environment.
It's like changing the wallpaper by taking a screenshot of the desktop, changing the screenshot, then setting it as the wallpaper, then asking how to remove the icons from the wallpaper.
The solution is to simply get the current unexpanded PATH setting from the registry instead of the expanded one from the environment.

`std::filesystem::path::operator/(/*args*/)` not working as expected

I have a class with an initialiser list in the constructor where one of the fields I'm initialising is a std::filesystem::path but it doesn't seem to be initialising to the expected value.
MyClass::MyClass(
unsigned int deviceSerial,
const std::string& processName
) :
deviceSerial(deviceSerial),
processName(processName),
configFilePath(GetBasePath() / std::to_string(deviceSerial) / ("#" + processName + ".json"))
{
/* Parameter checks */
}
Using the debugger I can see that GetBasePath() is returning exactly what I expect (returns std::filesystem::path with correct path) but the / operator doesn't seem to be having an effect. Once inside the body of the constructor I can see that configFilePath is setup to the result of GetBasePath() without the extra info appended.
I'm using MSVS-2019, I have the C++ language standard set to C++17 and in debug mode I have all optimisations disabled.
I have also tested the following in the body of the class and I still see path as simply the result of GetBasePath() and the extra items are not being appended.
{
auto path = GetBasePath(); // path = "C:/Users/Me/Desktop/Devices"
path /= std::to_string(deviceSerial); // path = "C:/Users/Me/Desktop/Devices"
path /= ("#" + processName + ".json"); // path = "C:/Users/Me/Desktop/Devices"
}
On a slight side note I also tried the above test with += instead of /= and I still see the same results.
Edit
As requested, below is a minimal complete and verifiable example.
#include <Windows.h>
#include <cstdio>
#include <filesystem>
#include <memory>
#include <string>
std::string ExpandPath(const std::string &str) {
auto reqBufferLen = ExpandEnvironmentStrings(str.c_str(), nullptr, 0);
if (reqBufferLen == 0) {
throw std::system_error((int)GetLastError(), std::system_category(),
"ExpandEnvironmentStrings() failed.");
}
auto buffer = std::make_unique<char[]>(reqBufferLen);
auto setBufferLen =
ExpandEnvironmentStrings(str.c_str(), buffer.get(), reqBufferLen);
if (setBufferLen != reqBufferLen - 1) {
throw std::system_error((int)GetLastError(), std::system_category(),
"ExpandEnvironmentStrings() failed.");
}
return std::string{buffer.get(), setBufferLen};
}
int main() {
unsigned int serial = 12345;
std::string procName = "Bake";
std::filesystem::path p(ExpandPath("%USERPROFILE%\\Desktop\\Devices"));
std::printf("Path = %s\n", p.string().c_str());
// p = C:\Users\Me\Desktop\Devices
p /= std::to_string(serial);
std::printf("Path = %s\n", p.string().c_str());
// p = C:\Users\Me\Desktop\Devices
p /= "#" + procName + ".json";
std::printf("Path = %s\n", p.string().c_str());
// p = C:\Users\Me\Desktop\Devices
std::getchar();
}
I've also used this example and tested with `p.append()` and got the same result.
I'd like to give thanks to #rustyx and #Frank for their suggestions, following this advice has led me to discover a bug in the way I create the initial string that gets passed to the path constructor (Also #M.M who found the exact bug while I was typing this answer)
I created a function (that is in use in my class) std::string ExpandPath(const std::string& path) which uses the Windows API to expand any environment variables in a path and return a string. This string is generated from a char* and a count, that count includes the null byte so when creating an string using the constructor variant std::string(char* cstr, size_t len) this includes the null byte in the string itself.
Because I was using the debugger to interrogate the variables it reads C-style strings and stops at the null byte. In my original example I also use printf() as I just happen to prefer this function for output, but again this stops printing at the null byte. If I change the output to use std::cout I can see that the output has the expected path but with an extra space their (the null byte being printed as a space). Using std::cout I see that my paths result as the following with each append:
Path = C:\Users\Me\Desktop\Devices
Path = C:\Users\Me\Desktop\Devices \12345
Path = C:\Users\Me\Desktop\Devices \12345\#Bake.json
Summary:
Bug in my ExpandPath() where
return std::string{buffer.get(), setBufferLen};
Should be
return std::string{buffer.get(), setBufferLen - 1};

count the number of directories in a folder C++ windows

I have written a Java program that at one point counts the number of folders in a directory. I would like to translate this program into C++ (I'm trying to learn it). I've been able to translate most of the program, but I haven't been able to find a way to count the subdirectories of a directory. How would I accomplish this?
Thanks in advance
Here is an implementation using the Win32 API.
SubdirCount takes a directory path string argument and it returns a count of its immediate child subdirectories (as an int). Hidden subdirectories are included, but any named "." or ".." are not counted.
FindFirstFile is a TCHAR-taking alias which ends up as either FindFirstFileA or FindFirstFileW. In order to keep strings TCHAR, without assuming availabilty of CString, the example here includes some awkward code just for appending "/*" to the function's argument.
#include <tchar.h>
#include <windows.h>
int SubdirCount(const TCHAR* parent_path) {
// The hideous string manipulation code below
// prepares a TCHAR wildcard string (sub_wild)
// matching any subdirectory immediately under
// parent_path by appending "\*"
size_t len = _tcslen(parent_path);
const size_t alloc_len = len + 3;
TCHAR* sub_wild = new TCHAR[alloc_len];
_tcscpy_s(sub_wild, alloc_len, parent_path);
if(len && sub_wild[len-1] != _T('\\')) { sub_wild[len++] = _T('\\'); }
sub_wild[len++] = _T('*');
sub_wild[len++] = _T('\0');
// File enumeration starts here
WIN32_FIND_DATA fd;
HANDLE hfind;
int count = 0;
if(INVALID_HANDLE_VALUE != (hfind = FindFirstFile(sub_wild, &fd))) {
do {
if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// is_alias_dir is true if directory name is "." or ".."
const bool is_alias_dir = fd.cFileName[0] == _T('.') &&
(!fd.cFileName[1] || (fd.cFileName[1] == _T('.') &&
!fd.cFileName[2]));
count += !is_alias_dir;
}
} while(FindNextFile(hfind, &fd));
FindClose(hfind);
}
delete [] sub_wild;
return count;
}

C++ STL Set: Cannot find() last element inserted

I am in the process of writing an application in which I use the Set class in the C++ STL. I've discovered that the call to set->find() always seems to fail when I query for the last element I inserted. However, if I iterate over the set, I am able to see the element I was originally querying for.
To try to get a grasp on what is going wrong, I've created a sample application that exhibits the same behavior that I am seeing. My test code is posted below.
For the actual application itself, I need to store pointers to objects in the set. Is this what is causing the weird behavior. Or is there an operator I need to overload in the class I am storing the pointer of?
Any help would be appreciated.
#include <stdio.h>
#include <set>
using namespace std;
#define MySet set<FileInfo *,bool(*)(const FileInfo *, const FileInfo*)>
class FileInfo
{
public:
FileInfo()
{
m_fileName = 0;
}
FileInfo( const FileInfo & file )
{
setFile( file.getFile() );
}
~FileInfo()
{
if( m_fileName )
{
delete m_fileName;
m_fileName = 0;
}
}
void setFile( const char * file )
{
if( m_fileName )
{
delete m_fileName;
}
m_fileName = new char[ strlen( file ) + 1 ];
strcpy( m_fileName, file );
}
const char * getFile() const
{
return m_fileName;
}
private:
char * m_fileName;
};
bool fileinfo_comparator( const FileInfo * f1, const FileInfo* f2 )
{
if( f1 && ! f2 ) return -1;
if( !f1 && f2 ) return 1;
if( !f1 && !f2 ) return 0;
return strcmp( f1->getFile(), f2->getFile() );
}
void find( MySet *s, FileInfo * value )
{
MySet::iterator iter = s->find( value );
if( iter != s->end() )
{
printf( "Found File[%s] at Item[%p]\n", (*iter)->getFile(), *iter );
}
else
{
printf( "No Item found for File[%s]\n", value->getFile() );
}
}
int main()
{
MySet *theSet = new MySet(fileinfo_comparator);
FileInfo * profile = new FileInfo();
FileInfo * shell = new FileInfo();
FileInfo * mail = new FileInfo();
profile->setFile( "/export/home/lm/profile" );
shell->setFile( "/export/home/lm/shell" );
mail->setFile( "/export/home/lm/mail" );
theSet->insert( profile );
theSet->insert( shell );
theSet->insert( mail );
find( theSet, profile );
FileInfo * newProfile = new FileInfo( *profile );
find( theSet, newProfile );
FileInfo * newMail = new FileInfo( *mail );
find( theSet, newMail );
printf( "\nDisplaying Contents of Set:\n" );
for( MySet::iterator iter = theSet->begin();
iter != theSet->end(); ++iter )
{
printf( "Item [%p] - File [%s]\n", *iter, (*iter)->getFile() );
}
}
The Output I get from this is:
Found File[/export/home/lm/profile] at Item[2d458]
Found File[/export/home/lm/profile] at Item[2d458]
No Item found for File[/export/home/lm/mail]
Displaying Contents of Set:
Item [2d478] - File [/export/home/lm/mail]
Item [2d468] - File [/export/home/lm/shell]
Item [2d458] - File [/export/home/lm/profile]
**Edit
It's kind of sad that I have to add this. But as I mentioned before, this is a sample application that was pulled from different parts of a larger application to exhibit the failure I was receiving.
It is meant as a unit test for calling set::find on a set populated with heap allocated pointers. If you have a problem with all the new()s, I'm open to suggestions on how to magically populate a set with heap allocated pointers without using them. Otherwise commenting on "too many new() calls" will just make you look silly.
Please focus on the actual problem that was occurring (which is now solved). Thanks.
***Edit
Perhaps I should have put these in my original question. But I was hoping there would be more focus on the problem with the find() (or as it turns out fileinfo_comparator function that acts more like strcmp than less), then a code review of a copy-paste PoC unit test.
Here are some points about the code in the full application itself.
FileInfo holds a lot of data along with the filename. It holds SHA1 sums, file size, mod time, system state at last edit, among other things. I have cut out must of it's code for this post. It violates the Rule of 3 in this form (Thanks #Martin York. See comments for wiki link).
The use of char* over std::string was originally chosen because of the use of 3rd_party APIs that accept char*. The app has since evolved from then. Changing this is not an option.
The data inside FileInfo is polled from a named pipe on the system and is stored in a Singleton for access across many threads. (I would have scope issues if I didn't allocate on heap)
I chose to store pointers in the Set because the FileInfo objects are large and constantly being added/removed from the Set. I decided pointers would be better than always copying large structures into the Set.
The if statement in my destructor is needless and a left over artifact from debugging of an issue I was tracking down. It should be pulled out because it is unneeded.
Your comparison function is wrong - it returns bool, not integer as strcmp(3). The return statement should be something like:
return strcmp( f1->getFile(), f2->getFile() ) < 0;
Take a look here.
Also, out of curiosity, why not just use std::set<std::string> instead? STL actually has decent defaults and frees you from a lot of manual memory management.
It looks to me like your FileInfo doesn't work correctly (at least for use in a std::set). To be stored in a std::set, the comparison function should return a bool indicating that the two parameters are in order (true) or out of order (false).
Given what your FileInfo does (badly designed imitation of std::string), you'd probably be better off without it completely. As far as I can see, you can use std::string in its place without any loss of functionality. You're also using a lot of dynamic allocation for no good reason (and leaking a lot of what you allocate).
#include <set>
#include <iostream>
#include <iterator>
#include <string>
int main() {
char *inputs[] = { "/export/home/lm/profile", "/export/home/lm/shell", "/export/home/lm/mail" };
char *outputs[] = {"Found: ", "Could **not** find: "};
std::set<std::string> MySet(inputs, inputs+3);
for (int i=0; i<3; i++)
std::cout
<< outputs[MySet.find(inputs[i]) == MySet.end()]
<< inputs[i] << "\n";
std::copy(MySet.begin(), MySet.end(),
std::ostream_iterator<std::string>(std::cout, "\n"));
return 0;
}
Edit: even when (or really, especially when) FileInfo is more complex, it shouldn't attempt to re-implement string functionality on its own. It should still use an std::string for the file name, and implement an operator< that works with that:
class FileInfo {
std::string filename;
public:
// ...
bool operator<(FileInfo const &other) const {
return filename < other.filename;
}
FileInfo(char const *name) : filename(name) {}
};
std::ostream &operator(std::ostream &os, FileInfo const &fi) {
return os << fi.filename;
}
int main() {
// std::set<std::string> MySet(inputs, inputs+3);
std:set<FileInfo> MySet(inputs, inputs+3);
// ...
std::copy(MySet.begin(), MySet.end(),
std::ostream_iterator<FileInfo>(std::cout, "\n"));
}
In your constructor:
FileInfo( const FileInfo & file )
{
setFile( file.getFile() );
}
m_fileName seems to be not initialized.

How can I refactor this to use an inline function or template instead of a macro?

I have a useful macro here:
#include <algorithm>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <Windows.h>
namespace Path {
bool Exists(const std::wstring& path)
{
DWORD result = GetFileAttributesW(path.c_str());
return result != INVALID_FILE_ATTRIBUTES;
}
// THIS IS THE MACRO IN QUESTION!
#define PATH_PREFIX_RESOLVE(path, prefix, environment) \
if (boost::algorithm::istarts_with(path, prefix)) { \
ExpandEnvironmentStringsW(environment, buffer, MAX_PATH); \
path.replace(0, (sizeof(prefix)/sizeof(wchar_t)) - 1, buffer); \
if (Exists(path)) return path; \
}
std::wstring Resolve(std::wstring path)
{
using namespace boost::algorithm;
wchar_t buffer[MAX_PATH];
trim(path);
if (path.empty() || Exists(path)) return path;
//Start by trying to see if we have a quoted path
if (path[0] == L'"') {
return std::wstring(path.begin() + 1, std::find(path.begin() + 1, path.end(), L'"'));
}
//Check for those nasty cases where the beginning of the path has no root
PATH_PREFIX_RESOLVE(path, L"\\", L"");
PATH_PREFIX_RESOLVE(path, L"?\?\\", L"");
PATH_PREFIX_RESOLVE(path, L"\\?\\", L"");
PATH_PREFIX_RESOLVE(path, L"globalroot\\", L"");
PATH_PREFIX_RESOLVE(path, L"system32\\", L"%systemroot%\\System32\\");
PATH_PREFIX_RESOLVE(path, L"systemroot\\", L"%systemroot%\\");
static std::vector<std::wstring> pathExts;
if (pathExts.empty()) {
#define MAX_ENVVAR 32767
wchar_t pathext[MAX_ENVVAR];
DWORD length = GetEnvironmentVariableW(L"PATHEXT", pathext, MAX_ENVVAR);
if (!length) WindowsApiException::ThrowFromLastError();
split(pathExts, pathext, std::bind2nd(std::equal_to<wchar_t>(), L';'));
pathExts.insert(pathExts.begin(), std::wstring());
}
std::wstring::iterator currentSpace = path.begin();
do {
currentSpace = std::find(currentSpace, path.end(), L' ');
std::wstring currentPath(path.begin(), currentSpace);
std::wstring::size_type currentPathLength = currentPath.size();
typedef std::vector<std::wstring>::const_iterator ExtIteratorType;
for(ExtIteratorType it = pathExts.begin(); it != pathExts.end(); it++) {
currentPath.replace(currentPathLength, currentPath.size() - currentPathLength, *it);
if (Exists(currentPath)) return currentPath;
}
if (currentSpace != path.end())
currentSpace++;
} while (currentSpace != path.end());
return path;
}
}
It's used about 6 times within the scope of a single function (that's it), but macros seem to have "bad karma" :P
Anyway, the problem here is the sizeof(prefix) part of the macro. If I just replace this with a function taking a const wchar_t[], then the sizeof() will fail to deliver expected results.
Simply adding a size member doesn't really solve the problem either. Making the user supply the size of the constant literal also results in a mess of duplicated constants at the call site.
Any ideas on this one?
Pass the array by reference, using a template to infer the length. I'll go looking for an example, but basically:
template<size_t N>
bool func(const char (&a)[N], blah, blah) { ... }
EDIT: Someone explained it here:
http://heifner.blogspot.com/2008/04/c-array-size-determination.html
Why not just make it a regular function that uses wcslen() to get the length of the parameter you're interested in. There's enough stuff going on in that macro/function that I imagine there's little value trying to force it to be inlined. The 'overhead; of the wcslen() call and processing is almost certainly not going to be a bottleneck.
The only trick in the macro that you really should be concerned with is (as GMan pointed out) the return from the within the macro that's hidden when the macro is invoked.
Just have the thing be a function that returns a success/fail, and you can return if the function succeeds:
bool PathPrefixResolve( std::wstring& path, wchar_t const* prefix, wchar_t const* environment)
{
wchar_t buffer[MAX_PATH];
if (boost::algorithm::istarts_with(path, prefix)) {
ExpandEnvironmentStringsW( environment, buffer, MAX_PATH);
std::wstring tmp( path);
tmp.replace(0, wcslen( prefix), buffer);
if (Exists(tmp)) {
path = tmp;
return true;
}
}
return false;
}
to use the function:
//Check for those nasty cases where the beginning of the path has no root
if (PathPrefixResolve2(path, L"\\", L"")) return path;
if (PathPrefixResolve2(path, L"?\?\\", L"")) return path;
if (PathPrefixResolve2(path, L"\\?\\", L"")) return path;
if (PathPrefixResolve2(path, L"globalroot\\", L"")) return path;
if (PathPrefixResolve2(path, L"system32\\", L"%systemroot%\\System32\\")) return path;
if (PathPrefixResolve2(path, L"systemroot\\", L"%systemroot%\\")) return path;
Given what the processing that's occurring in the macro, I don't think you need to be worried about function call overhead.
Also, your macro implementation has some behavior which I think is probably a bug - if the path starts with L"\\?\\" that means it also starts with L"\\" and your first invocation of the macro:
PATH_PREFIX_RESOLVE(path, L"\\", L"");
will change the path variable. As the program gets maintained and additional prefixes get added, the problem could be seen with other path prefixes. This bug isn't in the function version, since the function changes the path parameter only when there's a verified match.
However, there's still possibly an issue when dealing with the L"\\?\\" and L"\\" prefixes in that both might be a match - you need to make sure you pass in the prefixes that might match more than once in 'priority' order.
Have you tried replacing sizeof(prefix)/sizeof(wchar_t) with wcslen(prefix)?