Reputation: 19
In C++ command-line program I am working, I require to find path of execution i.e. path from where .exe file is called. For example, if file is stored if D:\stored\location\
but it is called from some other directory , like D:\executed\here\
, I should get D:\executed\here\
as path.
Command prompt should look somewhat like this:-
D:\executed\here> D:\stored\location\program_name.exe getpath
Path of execution is : D:\executed\here
I tried to use GetModuleFileName as explained here and here, but I got \stored\here
.
Is there a cross-platform way to find "execution path"?
Note:- I am using VSCode.
Edit: version of compiler is: g++ (i686-posix-dwarf-rev0, Built by MinGW-W64 project) 8.1.0
Side note:- for some reason after including #include<filesystem>
, std::filesystem
is showing error : 'std::filesystem' has not been declared", so i am not able to use filesystem.h
Upvotes: 0
Views: 1066
Reputation: 20141
The MinGW seems to be shipped currently with g++ 6.3 which I consider as quite old. So, I wrote another Answer for C++11.
Incidentally, I solved the same problem with the help of
SO: Get path of executable
a few days ago.
I've put the relevant part into an MCVE:
// Declaration (Header):
// standard C++ header:
#include <filesystem>
// returns file path of this executable
std::filesystem::path getExecPath();
/**************************************************************************/
// Definition (C++ Source):
// standard C++ header:
#include <string>
// OS header:
#ifdef _MSC_VER // Is this MSVC?
#include <windows.h>
#else // (not) _MSC_VER // Then it's hopefully Linux/g++.
#include <unistd.h>
#endif // _MSC_VER
std::filesystem::path getExecPath()
{
#ifdef _MSC_VER // Is this MSVC?
std::wstring path(1024, L'\0');
const DWORD len
= GetModuleFileNameW(NULL, &path[0], (DWORD)path.size());
if (!len) return std::filesystem::path(); // ERROR!
path.resize(len);
return std::filesystem::path(path);
#else // (not) _MSC_VER // Then it's hopefully Linux/g++.
std::string path(1024, '\0');
ssize_t len
= readlink("/proc/self/exe", &path[0], path.size());
if (len < 0) return std::filesystem::path(); // ERROR!
path.resize(len);
return path;
#endif // _MSC_VER
}
/**************************************************************************/
// Test:
// standard C++ header:
#include <iostream>
int main()
{
std::cout
<< "Exec. Path: " << getExecPath() << '\n'
<< "Current Dir.: " << std::filesystem::current_path() << '\n';
}
First, I tried on coliru:
g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && mkdir test ; cd test ; ../a.out
Exec. Path: "/tmp/1609670485.1182494/a.out"
Current Dir.: "/tmp/1609670485.1182494/test"
Note: The version of g++
on coliru is g++ 10.2.0 (at the time of writing).
I added a CMakeLists.txt
to test this on my local box:
project(ExecPath)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (UNIX)
# std::filesystem which was added in C++17
# seems to need an extra lib. in g++.
# This might be version dependent...
link_libraries(-lstdc++fs)
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
add_executable(testExecPath testExecPath.cc)
The MCVE built and run in Visual Studion 2019.
Output:
Exec. Path: "D:\\ds32737\\Entwicklung\\tests\\C++\\execPath\\build-VS2019\\bin\\Debug\\testExecPath.exe"
Current Dir.: "D:\\ds32737\\Entwicklung\\tests\\C++\\execPath\\build-VS2019"
Finally, I tested this on Debian-Linux (in a VM with g++ 8.3.0).
Test Session:
ds32737@debian:/mnt/hostd/Entwicklung/tests/C++/execPath$ mkdir build-debian
ds32737@debian:/mnt/hostd/Entwicklung/tests/C++/execPath$ cd build-debian/
ds32737@debian:/mnt/hostd/Entwicklung/tests/C++/execPath/build-debian$ cmake ..
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/hostd/Entwicklung/tests/C++/execPath/build-debian
ds32737@debian:/mnt/hostd/Entwicklung/tests/C++/execPath/build-debian$ cmake --build .
Scanning dependencies of target testExecPath
[ 50%] Building CXX object CMakeFiles/testExecPath.dir/testExecPath.cc.o
[100%] Linking CXX executable bin/testExecPath
[100%] Built target testExecPath
ds32737@debian:/mnt/hostd/Entwicklung/tests/C++/execPath/build-debian$ bin/testExecPath
Exec. Path: "/mnt/hostd/Entwicklung/tests/C++/execPath/build-debian/bin/testExecPath"
Current Dir.: "/mnt/hostd/Entwicklung/tests/C++/execPath/build-debian"
ds32737@debian:/mnt/hostd/Entwicklung/tests/C++/execPath/build-debian$
The g++ 8.3
is the default on my Debian installation which I did quite recently. It's a bit aged.
So, I had to add -lstdc++fs
to come around link issues with std::filesystem
.
Please, note, that this was not necessary with g++ 10.2
I used in coliru.
Please, note that I took care that the current working directory was not the directory where the executable is located in, in every case.
For the solution of this task, I took care about encoding issues. (In the past, I've often struggled with file systems and encoding issues as soon as everything else than ASCII characters appear in file paths.)
Thus, I used GetModuleHandleW()
for the Windows implementation which returns the file path in UTF-16.
On Linux, I assume that UTF-8 is used always.
Strictly speaking, std::filesystem::path
is even not really necessary.
Instead, the path could be returned e.g. as std::string
.
For this, the Windows impl. could convert UTF-16 to UTF-8 to provide a granted encoding on any platform.
Upvotes: 1
Reputation: 20141
Out of curiosity, I installed the MinGW g++ into my git-bash (following How to install gcc in Git Bash (Windows). That worked immediately but the first bad surprise came soon:
ds32737@lapeks415-017 MINGW64 /d/ds32737/Entwicklung/tests/C++/execPath
$ which g++
/d/MinGW/bin/g++
ds32737@lapeks415-017 MINGW64 /d/ds32737/Entwicklung/tests/C++/execPath
$ g++ --version
g++.exe (MinGW.org GCC-6.3.0-1) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Oh, oops! g++ 6.3. That explains OPs problems with std::filesystem
(and I got them on my side too).
So, I modified the other answer to back-port this to C++11.
My C++11 conform MCVE:
// Declaration (Header):
// standard C++ header:
#include <string>
// returns file path of this executable
std::string getExecPath();
/**************************************************************************/
// Definition (C++ Source):
// standard C++ header:
#include <cstring>
#include <codecvt>
#include <locale>
// OS header:
#ifdef _WIN32 // Is this Windows?
#include <windows.h>
#else // (not) _WIN32 // Then it's hopefully Linux.
#include <unistd.h>
#endif // _WIN32
std::string getExecPath()
{
#ifdef _WIN32 // Is this Windows?
std::wstring path(1024, L'\0');
const DWORD len
= GetModuleFileNameW(NULL, &path[0], (DWORD)path.size());
if (!len) return std::string(); // ERROR!
path.resize(len);
std::wstring_convert<std::codecvt_utf8<wchar_t>> convU16ToU8;
return convU16ToU8.to_bytes(path);
#else // (not) _WIN32 // Then it's hopefully Linux.
std::string path(1024, '\0');
ssize_t len
= readlink("/proc/self/exe", &path[0], path.size());
if (len < 0) return std::string(); // ERROR!
path.resize(len);
return path;
#endif // _WIN32
}
std::string getCWD()
{
#ifdef _WIN32 // Is this Windows?
std::wstring path(1024, L'\0');
const DWORD len
= GetCurrentDirectoryW(path.size(), &path[0]);
if (!len) return std::string(); // ERROR!
path.resize(len);
std::wstring_convert<std::codecvt_utf8<wchar_t>> convU16ToU8;
return convU16ToU8.to_bytes(path);
#else // (not) _WIN32 // Then it's hopefully Linux.
std::string path(1024, '\0');
if (!getcwd(&path[0], path.size())) return std::string(); // ERROR!
size_t len = std::strlen(path.c_str());
path.resize(len);
return path;
#endif // _WIN32
}
/**************************************************************************/
// Test:
// standard C++ header:
#include <iostream>
int main()
{
std::cout
<< "Exec. Path: " << getExecPath() << '\n'
<< "Current Dir.: " << getCWD() << '\n';
}
Test Session in MinGW (g++ 6.3):
ds32737@lapeks415-017 MINGW64 /d/ds32737/Entwicklung/tests/C++/execPath/C++11
$ g++ -std=c++11 -O2 testExecPath.cc && mkdir test ; cd test ; ../a.exe
Exec. Path: D:\ds32737\Entwicklung\tests\C++\execPath\C++11\a.exe
Current Dir.: D:\ds32737\Entwicklung\tests\C++\execPath\C++11\test
ds32737@lapeks415-017 MINGW64 /d/ds32737/Entwicklung/tests/C++/execPath/C++11/test
$
Test on coliru:
g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && mkdir test ; cd test ; ../a.out
Exec. Path: /tmp/1609678428-623235554/a.out
Current Dir.: /tmp/1609678428-623235554/test
Note:
I was not aware of this as I've never used g++ in MinGW before:
_WIN32
is defined in MinGW as well, and the respective code is used (and has to be used).
(Before, I used _MSC_VER
which activates the Windows code for MSVC only.
In this case, I got a compiler error as readlink()
was not available.
Even if it would've been it probably hadn't done what it's supposed to on Linux.)
Upvotes: 1