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

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

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.

C++ Experimental filesystem has no "relative" function?

I'm stuck with GCC7.1 for which I have to use #include <experimental/filesystem> instead of #include <filesystem>.
So I have namespace fs = std::experimental::filesystem; and then in the code I use fs::relative(p, base) which gives me error: ‘relative’ is not a member of ‘fs’.
It was working with normal C++17 filesystem, but doesn't seem to be present in experimental::filesystem. How can I use relative function with experimental::filesystem?
In addition to https://stackoverflow.com/a/63900421/9323999, it would be better to call weakly_canonical() backport instead of absolute() to properly handle '.' and '..' in paths. And as the answer above "(no complete solution - some extra edge cases need to be handled)":
[[nodiscard]] inline path weakly_canonical(path p)
{
// 1. convert p to an absolute path
p = absolute(p);
// 2. declare special strings to handle
using path_sv = std::basic_string_view<path::value_type>;
static constexpr path_sv dot{"."};
static constexpr path_sv dotdot{".."};
// 3. iterate `p` to handle its parts
path ret;
for (const auto &part : p)
if (part == dot)
continue; // skip '.'
else if (part == dotdot)
ret = ret.parent_path(); // move to parent path on '..'
else
ret /= part; // add other entries to the result
return ret;
}
You have to use third-party implementation (for ex. boost) or provide your own, something like (no complete solution - some extra edge cases need to be handled):
path relative(path p, path base)
{
// 1. convert p and base to absolute paths
p = fs::absolute(p);
base = fs::absolute(base);
// 2. find first mismatch and shared root path
auto mismatched = std::mismatch(p.begin(), p.end(), base.begin(), base.end());
// 3. if no mismatch return "."
if (mismatched.first == p.end() && mismatched.second == base.end())
return ".";
auto it_p = mismatched.first;
auto it_base = mismatched.second;
path ret;
// 4. iterate abase to the shared root and append "../"
for (; it_base != base.end(); ++it_base) ret /= "..";
// 5. iterate from the shared root to the p and append its parts
for (; it_p != p.end(); ++it_p) ret /= *it_p;
return ret;
}

Get relative path from two absolute paths

I have two absolute filesystem paths (A and B), and I want to generate a third filesystem path that represents "A relative from B".
Use case:
Media player managing a playlist.
User adds file to playlist.
New file path added to playlist relative to playlist path.
In the future, entire music directory (including playlist) moved elsewhere.
All paths still valid because they are relative to the playlist.
boost::filesystem appears to have complete to resolve relative ~ relative => absolute, but nothing to do this in reverse (absolute ~ absolute => relative).
I want to do it with Boost paths.
With C++17 and its std::filesystem::relative, which evolved from boost, this is a no-brainer:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main()
{
const fs::path base("/is/the/speed/of/light/absolute");
const fs::path p("/is/the/speed/of/light/absolute/or/is/it/relative/to/the/observer");
const fs::path p2("/little/light/races/in/orbit/of/a/rogue/planet");
std::cout << "Base is base: " << fs::relative(p, base).generic_string() << '\n'
<< "Base is deeper: " << fs::relative(base, p).generic_string() << '\n'
<< "Base is orthogonal: " << fs::relative(p2, base).generic_string();
// Omitting exception handling/error code usage for simplicity.
}
Output (second parameter is base)
Base is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet
It uses std::filesystem::path::lexically_relative for comparison.
The difference to the pure lexical function is, that std::filesystem::relative resolves symlinks and normalizes both paths using
std::filesystem::weakly_canonical (which was introduced for relative) before comparison.
As of version 1.60.0 boost.filesystem does support this. You're looking for the member function path lexically_relative(const path& p) const.
Original, pre-1.60.0 answer below.
Boost doesn't support this; it's an open issue — #1976 (Inverse function for complete) — that nevertheless doesn't seem to be getting much traction.
Here's a vaguely naive workaround that seems to do the trick (not sure whether it can be improved):
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <stdexcept>
/**
* https://svn.boost.org/trac/boost/ticket/1976#comment:2
*
* "The idea: uncomplete(/foo/new, /foo/bar) => ../new
* The use case for this is any time you get a full path (from an open dialog, perhaps)
* and want to store a relative path so that the group of files can be moved to a different
* directory without breaking the paths. An IDE would be a simple example, so that the
* project file could be safely checked out of subversion."
*
* ALGORITHM:
* iterate path and base
* compare all elements so far of path and base
* whilst they are the same, no write to output
* when they change, or one runs out:
* write to output, ../ times the number of remaining elements in base
* write to output, the remaining elements in path
*/
boost::filesystem::path
naive_uncomplete(boost::filesystem::path const p, boost::filesystem::path const base) {
using boost::filesystem::path;
using boost::filesystem::dot;
using boost::filesystem::slash;
if (p == base)
return "./";
/*!! this breaks stuff if path is a filename rather than a directory,
which it most likely is... but then base shouldn't be a filename so... */
boost::filesystem::path from_path, from_base, output;
boost::filesystem::path::iterator path_it = p.begin(), path_end = p.end();
boost::filesystem::path::iterator base_it = base.begin(), base_end = base.end();
// check for emptiness
if ((path_it == path_end) || (base_it == base_end))
throw std::runtime_error("path or base was empty; couldn't generate relative path");
#ifdef WIN32
// drive letters are different; don't generate a relative path
if (*path_it != *base_it)
return p;
// now advance past drive letters; relative paths should only go up
// to the root of the drive and not past it
++path_it, ++base_it;
#endif
// Cache system-dependent dot, double-dot and slash strings
const std::string _dot = std::string(1, dot<path>::value);
const std::string _dots = std::string(2, dot<path>::value);
const std::string _sep = std::string(1, slash<path>::value);
// iterate over path and base
while (true) {
// compare all elements so far of path and base to find greatest common root;
// when elements of path and base differ, or run out:
if ((path_it == path_end) || (base_it == base_end) || (*path_it != *base_it)) {
// write to output, ../ times the number of remaining elements in base;
// this is how far we've had to come down the tree from base to get to the common root
for (; base_it != base_end; ++base_it) {
if (*base_it == _dot)
continue;
else if (*base_it == _sep)
continue;
output /= "../";
}
// write to output, the remaining elements in path;
// this is the path relative from the common root
boost::filesystem::path::iterator path_it_start = path_it;
for (; path_it != path_end; ++path_it) {
if (path_it != path_it_start)
output /= "/";
if (*path_it == _dot)
continue;
if (*path_it == _sep)
continue;
output /= *path_it;
}
break;
}
// add directory level to both paths and continue iteration
from_path /= path(*path_it);
from_base /= path(*base_it);
++path_it, ++base_it;
}
return output;
}
I just wrote code that can translate an absolute path to a relative path. It works in all my use cases, but I can not guarantee it is flawless.
I have abreviated boost::filesystem to 'fs' for readability. In the function definition, you can use fs::path::current_path() as a default value for 'relative_to'.
fs::path relativePath( const fs::path &path, const fs::path &relative_to )
{
// create absolute paths
fs::path p = fs::absolute(path);
fs::path r = fs::absolute(relative_to);
// if root paths are different, return absolute path
if( p.root_path() != r.root_path() )
return p;
// initialize relative path
fs::path result;
// find out where the two paths diverge
fs::path::const_iterator itr_path = p.begin();
fs::path::const_iterator itr_relative_to = r.begin();
while( itr_path != p.end() && itr_relative_to != r.end() && *itr_path == *itr_relative_to ) {
++itr_path;
++itr_relative_to;
}
// add "../" for each remaining token in relative_to
if( itr_relative_to != r.end() ) {
++itr_relative_to;
while( itr_relative_to != r.end() ) {
result /= "..";
++itr_relative_to;
}
}
// add remaining path
while( itr_path != p.end() ) {
result /= *itr_path;
++itr_path;
}
return result;
}
I was just thinking about using boost::filesystem for the same task, but - since my application uses both Qt and Boost libraries, I decided to use Qt which does this task with one simple method QString QDir::relativeFilePath( const QString & fileName ):
QDir dir("/home/bob");
QString s;
s = dir.relativeFilePath("images/file.jpg"); // s is "images/file.jpg"
s = dir.relativeFilePath("/home/mary/file.txt"); // s is "../mary/file.txt"
It works like a charm and saved me a few hours of my life.
Here's how I do it in the library I build on top of boost filesystem:
Step 1: Determine "deepest common root". Basically, its like the greatest common denominator for 2 paths. For example, if you're 2 paths are "C:\a\b\c\d" and "C:\a\b\c\l.txt" then the common root they both share is "C:\a\b\c\".
To get this, convert both paths into absolute- NOT canonical- form (you'll want to be able to do this for speculative paths & symlinks).
Step 2: To go from A to B, you suffix A with enough copies of "../" to shift up the directory tree to the common root, then add the string for B to travel down the tree to it. On windows you can have 2 paths with no common root, so going from any A to any B is not always possible.
namespace fs = boost::filesystem;
bool GetCommonRoot(const fs::path& path1,
const fs::path& path2,
fs::path& routeFrom1To2,
std::vector<fs::path>& commonDirsInOrder)
{
fs::path pathA( fs::absolute( path1));
fs::path pathB( fs::absolute( path2));
// Parse both paths into vectors of tokens. I call them "dir" because they'll
// be the common directories unless both paths are the exact same file.
// I also Remove the "." and ".." paths as part of the loops
fs::path::iterator iter;
std::vector<fs::path> dirsA;
std::vector<fs::path> dirsB;
for(iter = pathA.begin(); iter != pathA.end(); ++iter) {
std::string token = (*iter).string();
if(token.compare("..") == 0) { // Go up 1 level => Pop vector
dirsA.pop_back();
}
else if(token.compare(".") != 0) { // "." means "this dir" => ignore it
dirsA.push_back( *iter);
}
}
for(iter = pathB.begin(); iter != pathB.end(); ++iter) {
std::string token = (*iter).string();
if(token.compare("..") == 0) { // Go up 1 level => Pop vector
dirsB.pop_back();
}
else if(token.compare(".") != 0) { // "." means "this dir" => ignore it
dirsB.push_back( *iter);
}
}
// Determine how far to check in each directory set
size_t commonDepth = std::min<int>( dirsA.size(), dirsB.size());
if(!commonDepth) {
// They don't even share a common root- no way from A to B
return false;
}
// Match entries in the 2 vectors until we see a divergence
commonDirsInOrder.clear();
for(size_t i=0; i<commonDepth; ++i) {
if(dirsA[i].string().compare( dirsB[i].string()) != 0) { // Diverged
break;
}
commonDirsInOrder.push_back( dirsA[i]); // I could use dirsB too.
}
// Now determine route: start with A
routeFrom1To2.clear();
for(size_t i=0; i<commonDepth; ++i) {
routeFrom1To2 /= dirsA[i];
}
size_t backupSteps = dirsA.size() - commonDepth; // # of "up dir" moves we need
for(size_t i=0; i<backupSteps; ++i) {
routeFrom1To2 /= "../";
}
// Append B's path to go down to it from the common root
for(size_t i=commonDepth; i<dirsB.size(); ++i) {
routeFrom1To2 /= dirsB[i]; // ensures absolutely correct subdirs
}
return true;
}
This will do what you want- you go up from A until you hit the common folder it and B are both descendants of, then go down to B. You probably don't need the "commonDirsInOrder" return that I have, but the "routeFrom1To2" return IS the one you're asking for.
If you plan to actually change the working directory to "B" you can use "routeFrom1To2" directly. Be aware that this function will produce an absolute path despite all the ".." parts, but that shouldn't be a problem.
I have write down one simple solution for this trick.
There's no usage on boost libraries, only STL's std::string, std::vector.
The Win32 platform has been tested.
Just calling:
strAlgExeFile = helper.GetRelativePath(PathA, PathB);
And, it would return relative path from PathA to PathB.
Example:
strAlgExeFile = helper.GetRelativePath((helper.GetCurrentDir()).c_str(), strAlgExeFile.c_str());
#ifdef _WIN32
#define STR_TOKEN "\\"
#define LAST_FOLDER "..\\"
#define FOLDER_SEP "\\"
#define LINE_BREAK "\r\n"
#else
#define STR_TOKEN "/"
#define LAST_FOLDER "../"
#define FOLDER_SEP "/"
#define LINE_BREAK "\n"
#endif // _WIN32
void CHelper::SplitStr2Vec(const char* pszPath, vector<string>& vecString)
{
char * pch;
pch = strtok (const_cast < char*> (pszPath), STR_TOKEN );
while (pch != NULL)
{
vecString.push_back( pch );
pch = strtok (NULL, STR_TOKEN );
}
}
string& CHelper::GetRelativePath(const char* pszPath1,const char* pszPath2)
{
vector<string> vecPath1, vecPath2;
vecPath1.clear();
vecPath2.clear();
SplitStr2Vec(pszPath1, vecPath1);
SplitStr2Vec(pszPath2, vecPath2);
size_t iSize = ( vecPath1.size() < vecPath2.size() )? vecPath1.size(): vecPath2.size();
unsigned int iSameSize(0);
for (unsigned int i=0; i<iSize; ++i)
{
if ( vecPath1[i] != vecPath2[i])
{
iSameSize = i;
break;
}
}
m_strRelativePath = "";
for (unsigned int i=0 ; i< (vecPath1.size()-iSameSize) ; ++i)
m_strRelativePath += const_cast<char *> (LAST_FOLDER);
for (unsigned int i=iSameSize ; i<vecPath2.size() ; ++i)
{
m_strRelativePath += vecPath2[i];
if( i < (vecPath2.size()-1) )
m_strRelativePath += const_cast<char *> (FOLDER_SEP);
}
return m_strRelativePath;
}
I needed to do this without Boost and the other std based solution didn't do it for me so I reimplemented it. As I was working on this I realized that I'd done it before too...
Anyway, it's not as complete as some of the others but might be useful to people. It's Windows-specific; changes to make it POSIX involve directory separator and case sensitivity in the string compare.
Shortly after I got this implemented and working I had to transfer the surrounding functionality to Python so all of this just boiled down to os.path.relpath(to, from).
static inline bool StringsEqual_i(const std::string& lhs, const std::string& rhs)
{
return _stricmp(lhs.c_str(), rhs.c_str()) == 0;
}
static void SplitPath(const std::string& in_path, std::vector<std::string>& split_path)
{
size_t start = 0;
size_t dirsep;
do
{
dirsep = in_path.find_first_of("\\/", start);
if (dirsep == std::string::npos)
split_path.push_back(std::string(&in_path[start]));
else
split_path.push_back(std::string(&in_path[start], &in_path[dirsep]));
start = dirsep + 1;
} while (dirsep != std::string::npos);
}
/**
* Get the relative path from a base location to a target location.
*
* \param to The target location.
* \param from The base location. Must be a directory.
* \returns The resulting relative path.
*/
static std::string GetRelativePath(const std::string& to, const std::string& from)
{
std::vector<std::string> to_dirs;
std::vector<std::string> from_dirs;
SplitPath(to, to_dirs);
SplitPath(from, from_dirs);
std::string output;
output.reserve(to.size());
std::vector<std::string>::const_iterator to_it = to_dirs.begin(),
to_end = to_dirs.end(),
from_it = from_dirs.begin(),
from_end = from_dirs.end();
while ((to_it != to_end) && (from_it != from_end) && StringsEqual_i(*to_it, *from_it))
{
++to_it;
++from_it;
}
while (from_it != from_end)
{
output += "..\\";
++from_it;
}
while (to_it != to_end)
{
output += *to_it;
++to_it;
if (to_it != to_end)
output += "\\";
}
return output;
}

How to get the stem of a filename from a path?

I want to extract a const char* filename from a const char* filepath. I tried with regex but failed:
const char* currentLoadedFile = "D:\files\file.lua";
char fileName[256];
if (sscanf(currentLoadedFile, "%*[^\\]\\%[^.].lua", fileName)) {
return (const char*)fileName; // WILL RETURN "D:\files\file!!
}
The issue is that "D:\files\file" will be returned and not the wanted "file"(note: without ".lua")
What about using std::string?
e.g.
std::string path("d:\\dir\\subdir\\file.ext");
std::string filename;
size_t pos = path.find_last_of("\\");
if(pos != std::string::npos)
filename.assign(path.begin() + pos + 1, path.end());
else
filename = path;
Just use boost::filesystem.
#include <boost/filesystem.hpp>
std::string filename_noext;
filename_noext = boost::filesystem::path("D:\\files\\file.lua").stem().string().
const char* result_as_const_char = filename_noext.c_str();
or alternatively, if you want to introduce bugs yourself :
// have fun defining that to the separator of the target OS.
#define PLATFORM_DIRECTORY_SEPARATOR '\\'
// the following code is guaranteed to have bugs.
std::string input = "D:\\files\\file.lua";
std::string::size_type filename_begin = input.find_last_of(PLATFORM_DIRECTORY_SEPERATOR);
if (filename_begin == std::string::npos)
filename_begin = 0;
else
filename_begin++;
std::string::size_type filename_length = input.find_last_of('.');
if (filename_length != std::string::npos)
filename_length = filename_length - filename_begin;
std::string result = input.substr(filename_begin, filename_length);
const char* bugy_result_as_const_char = result.c_str();
You can do this portably and easily using the new filesystem library in C++17.
#include <cstdint>
#include <cstdio>
#include <filesystem>
int main()
{
std::filesystem::path my_path("D:/files/file.lua");
std::printf("filename: %s\n", my_path.filename().u8string().c_str());
std::printf("stem: %s\n", my_path.stem().u8string().c_str());
std::printf("extension: %s\n", my_path.extension().u8string().c_str());
}
Output:
filename: file.lua
stem: file
extension: .lua
Do note that for the time being you may need to use #include <experimental/fileystem> along with std::experimental::filesystem instead until standard libraries are fully conforming.
For more documentation on std::filesystem check out the filesystem library reference.
You can easily extract the file:
int main()
{
char pscL_Dir[]="/home/srfuser/kush/folder/kushvendra.txt";
char pscL_FileName[50];
char pscL_FilePath[100];
char *pscL;
pscL=strrchr(pscL_Dir,'/');
if(pscL==NULL)
printf("\n ERROR :INvalid DIr");
else
{
strncpy(pscL_FilePath,pscL_Dir,(pscL-pscL_Dir));
strcpy(pscL_FileName,pscL+1);
printf("LENTH [%d}\n pscL_FilePath[%s]\n pscL_FileName[%s]",(pscL-pscL_Dir),pscL_FilePath,pscL_FileName);
}
return 0;
}
output:
LENTH [25}
pscL_FilePath[/home/srfuser/kush/folder]
pscL_FileName[kushvendra.txt
Here you can find an example. I'm not saying it's the best and I'm sure you could improve on that but it uses only standard C++ (anyway at least what's now considered standard).
Of course you won't have the features of the boost::filesystem (those functions in the example play along with plain strings and do not guarantee/check you'll actually working with a real filesystem path).
// Set short name:
char *Filename;
Filename = strrchr(svFilename, '\\');
if ( Filename == NULL )
Filename = svFilename;
if ( Filename[0] == '\\')
++Filename;
if ( !lstrlen(Filename) )
{
Filename = svFilename;
}
fprintf( m_FileOutput, ";\n; %s\n;\n", Filename );
You could use the _splitpath_s function to break a path name into its components. I don't know if this is standard C or is Windows specific. Anyway this is the function:
#include <stdlib.h>
#include <string>
using std::string;
bool splitPath(string const &path, string &drive, string &directory, string &filename, string &extension) {
// validate path
drive.resize(_MAX_DRIVE);
directory.resize(_MAX_DIR);
filename.resize(_MAX_FNAME);
extension.resize(_MAX_EXT);
errno_t result;
result = _splitpath_s(path.c_str(), &drive[0], drive.size(), &directory[0], directory.size(), &filename[0], filename.size(), &extension[0], extension.size());
//_splitpath(path.c_str(), &drive[0], &directory[0], &filename[0], &extension[0]); //WindowsXp compatibility
_get_errno(&result);
if (result != 0) {
return false;
} else {
//delete the blank spaces at the end
drive = drive.c_str();
directory = directory.c_str();
filename = filename.c_str();
extension = extension.c_str();
return true;
}
}
It is a lot easier and safe to use std::string but you could modify this to use TCHAR* (wchar, char)...
For your specific case:
int main(int argc, char *argv[]) {
string path = argv[0];
string drive, directory, filename, extension;
splitPath(path, drive, directory, filename, extension);
printf("FILE = %s%s", filename.c_str(), extension.c_str());
return 0;
}
If you are going to display a filename to the user on Windows you should respect their shell settings (show/hide extension etc).
You can get a filename in the correct format by calling SHGetFileInfo with the SHGFI_DISPLAYNAME flag.

Getting user temporary folder path in Windows

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