adish jain
adish jain

Reputation: 19

Finding path of execution of C++ program

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

Answers (2)

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

Answer for C++17

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

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

Answer for C++11

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

Related Questions