Uzyskaj ścieżkę do pliku wykonywalnego


115

Wiem, że to pytanie zadawano już wcześniej, ale nadal nie widziałem satysfakcjonującej odpowiedzi lub ostatecznego „nie, nie da się tego zrobić”, więc zapytam ponownie!

Chcę tylko uzyskać ścieżkę do aktualnie uruchomionego pliku wykonywalnego, jako ścieżkę bezwzględną lub w odniesieniu do miejsca, z którego plik wykonywalny jest wywoływany, w sposób niezależny od platformy. Myślałem, że boost :: filesystem :: initial_path był odpowiedzią na moje problemy, ale wydaje się, że rozwiązuje tylko część pytania „niezależną od platformy” - nadal zwraca ścieżkę, z której aplikacja została wywołana.

Dla trochę tła jest to gra wykorzystująca Ogre, którą próbuję profilować za pomocą Very Sleepy, która uruchamia docelowy plik wykonywalny z własnego katalogu, więc oczywiście po załadowaniu gra nie znajduje żadnych plików konfiguracyjnych itp. I natychmiast się zawiesza . Chcę móc przekazać mu absolutną ścieżkę do plików konfiguracyjnych, o których wiem, że zawsze będą istnieć razem z plikiem wykonywalnym. To samo dotyczy debugowania w Visual Studio - chciałbym móc uruchomić $ (TargetPath) bez konieczności ustawiania katalogu roboczego.



9
Pamiętaj, że nie można udowodnić braku odpowiedzi, dlatego nie można uzyskać ostatecznego NIE. Z przyjemnością udzielę Ci autorytatywnego NIE :)
MSalters


po załadowaniu gra nie znajduje plików konfiguracyjnych itp. ”, więc gra szuka plików konfiguracyjnych w bieżącym katalogu? To zły pomysł i potencjalnie luka w zabezpieczeniach. Pliki konfiguracyjne powinny być przechowywane w standardowej lokalizacji.
curiousguy

1
Opublikowałem tutaj odpowiedź na powiązane pytanie, które również odpowiada na twoje, pracując na różnych platformach przy użyciu funkcji boost
jtbr

Odpowiedzi:


86

Nie ma sposobu, który znam na wielu platformach.

W systemie Linux: readlink / proc / self / exe

Windows: GetModuleFileName


9
Niezależność platformy to po prostu kwestia ukrycia zależności platformy. W takim przypadku użycie wstępnie zdefiniowanych makr systemu operacyjnego opisanych szczegółowo w predef.sourceforge.net/preos.html do wybrania metody jest proste.
Clifford,

4
Czy to właśnie robią wszyscy, gdy chcą znaleźć ścieżkę do pliku wykonywalnego w C ++? Miałem nadzieję, że coś tak prostego, jak to, zostanie już zaimplementowane w bibliotece, na przykład boost.
Ben Hymers

2
@curiousguy Nie jestem pewien, czy cię rozumiem; Jestem pewien, że o to właśnie chodzi w tym pytaniu :)
Ben Hymers,

6
@curiousguy: Chciałbyś to zrobić, jeśli na przykład twój program może zostać zainstalowany w katalogu wybranym przez użytkownika. Musisz w jakiś sposób
greyfade

1
@Duck, czy zaktualizowałbyś swoją odpowiedź linkiem do mojej biblioteki? Mój komentarz jest ukryty na liście.
Gregory Pakosz

35

Plik boost :: dll :: program_location jest jedną z najlepszych znanych mi metod międzyplatformowego uzyskiwania ścieżki do działającego pliku wykonywalnego. Biblioteka DLL została dodana do Boost w wersji 1.61.0.

Oto moje rozwiązanie. Przetestowałem go na Windows, Mac OS X, Solaris, Free BSD i GNU / Linux.

Wymaga Boost 1.55.0 lub nowszego. Korzysta z biblioteki Boost.Filesystem bezpośrednio i Boost.Locale biblioteka i Boost.System biblioteki pośrednio.

src / executable_path.cpp

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iterator>
#include <string>
#include <vector>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/predef.h>
#include <boost/version.hpp>
#include <boost/tokenizer.hpp>

#if (BOOST_VERSION > BOOST_VERSION_NUMBER(1,64,0))
#  include <boost/process.hpp>
#endif

#if (BOOST_OS_CYGWIN || BOOST_OS_WINDOWS)
#  include <Windows.h>
#endif

#include <boost/executable_path.hpp>
#include <boost/detail/executable_path_internals.hpp>

namespace boost {

#if (BOOST_OS_CYGWIN || BOOST_OS_WINDOWS)

std::string executable_path(const char* argv0)
{
  typedef std::vector<char> char_vector;
  typedef std::vector<char>::size_type size_type;
  char_vector buf(1024, 0);
  size_type size = buf.size();
  bool havePath = false;
  bool shouldContinue = true;
  do
  {
    DWORD result = GetModuleFileNameA(nullptr, &buf[0], size);
    DWORD lastError = GetLastError();
    if (result == 0)
    {
      shouldContinue = false;
    }
    else if (result < size)
    {
      havePath = true;
      shouldContinue = false;
    }
    else if (
      result == size
      && (lastError == ERROR_INSUFFICIENT_BUFFER || lastError == ERROR_SUCCESS)
      )
    {
      size *= 2;
      buf.resize(size);
    }
    else
    {
      shouldContinue = false;
    }
  } while (shouldContinue);
  if (!havePath)
  {
    return detail::executable_path_fallback(argv0);
  }
  // On Microsoft Windows, there is no need to call boost::filesystem::canonical or
  // boost::filesystem::path::make_preferred. The path returned by GetModuleFileNameA
  // is the one we want.
  std::string ret = &buf[0];
  return ret;
}

#elif (BOOST_OS_MACOS)

#  include <mach-o/dyld.h>

std::string executable_path(const char* argv0)
{
  typedef std::vector<char> char_vector;
  char_vector buf(1024, 0);
  uint32_t size = static_cast<uint32_t>(buf.size());
  bool havePath = false;
  bool shouldContinue = true;
  do
  {
    int result = _NSGetExecutablePath(&buf[0], &size);
    if (result == -1)
    {
      buf.resize(size + 1);
      std::fill(std::begin(buf), std::end(buf), 0);
    }
    else
    {
      shouldContinue = false;
      if (buf.at(0) != 0)
      {
        havePath = true;
      }
    }
  } while (shouldContinue);
  if (!havePath)
  {
    return detail::executable_path_fallback(argv0);
  }
  std::string path(&buf[0], size);
  boost::system::error_code ec;
  boost::filesystem::path p(
    boost::filesystem::canonical(path, boost::filesystem::current_path(), ec));
  if (ec.value() == boost::system::errc::success)
  {
    return p.make_preferred().string();
  }
  return detail::executable_path_fallback(argv0);
}

#elif (BOOST_OS_SOLARIS)

#  include <stdlib.h>

std::string executable_path(const char* argv0)
{
  std::string ret = getexecname();
  if (ret.empty())
  {
    return detail::executable_path_fallback(argv0);
  }
  boost::filesystem::path p(ret);
  if (!p.has_root_directory())
  {
    boost::system::error_code ec;
    p = boost::filesystem::canonical(
      p, boost::filesystem::current_path(), ec);
    if (ec.value() != boost::system::errc::success)
    {
      return detail::executable_path_fallback(argv0);
    }
    ret = p.make_preferred().string();
  }
  return ret;
}

#elif (BOOST_OS_BSD)

#  include <sys/sysctl.h>

std::string executable_path(const char* argv0)
{
  typedef std::vector<char> char_vector;
  int mib[4]{0};
  size_t size;
  mib[0] = CTL_KERN;
  mib[1] = KERN_PROC;
  mib[2] = KERN_PROC_PATHNAME;
  mib[3] = -1;
  int result = sysctl(mib, 4, nullptr, &size, nullptr, 0);
  if (-1 == result)
  {
    return detail::executable_path_fallback(argv0);
  }
  char_vector buf(size + 1, 0);
  result = sysctl(mib, 4, &buf[0], &size, nullptr, 0);
  if (-1 == result)
  {
    return detail::executable_path_fallback(argv0);
  }
  std::string path(&buf[0], size);
  boost::system::error_code ec;
  boost::filesystem::path p(
    boost::filesystem::canonical(
      path, boost::filesystem::current_path(), ec));
  if (ec.value() == boost::system::errc::success)
  {
    return p.make_preferred().string();
  }
  return detail::executable_path_fallback(argv0);
}

#elif (BOOST_OS_LINUX)

#  include <unistd.h>

std::string executable_path(const char *argv0)
{
  typedef std::vector<char> char_vector;
  typedef std::vector<char>::size_type size_type;
  char_vector buf(1024, 0);
  size_type size = buf.size();
  bool havePath = false;
  bool shouldContinue = true;
  do
  {
    ssize_t result = readlink("/proc/self/exe", &buf[0], size);
    if (result < 0)
    {
      shouldContinue = false;
    }
    else if (static_cast<size_type>(result) < size)
    {
      havePath = true;
      shouldContinue = false;
      size = result;
    }
    else
    {
      size *= 2;
      buf.resize(size);
      std::fill(std::begin(buf), std::end(buf), 0);
    }
  } while (shouldContinue);
  if (!havePath)
  {
    return detail::executable_path_fallback(argv0);
  }
  std::string path(&buf[0], size);
  boost::system::error_code ec;
  boost::filesystem::path p(
    boost::filesystem::canonical(
      path, boost::filesystem::current_path(), ec));
  if (ec.value() == boost::system::errc::success)
  {
    return p.make_preferred().string();
  }
  return detail::executable_path_fallback(argv0);
}

#else

std::string executable_path(const char *argv0)
{
  return detail::executable_path_fallback(argv0);
}

#endif

}

src / detail / executable_path_internals.cpp

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iterator>
#include <string>
#include <vector>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/predef.h>
#include <boost/version.hpp>
#include <boost/tokenizer.hpp>

#if (BOOST_VERSION > BOOST_VERSION_NUMBER(1,64,0))
#  include <boost/process.hpp>
#endif

#if (BOOST_OS_CYGWIN || BOOST_OS_WINDOWS)
#  include <Windows.h>
#endif

#include <boost/executable_path.hpp>
#include <boost/detail/executable_path_internals.hpp>

namespace boost {
namespace detail {

std::string GetEnv(const std::string& varName)
{
  if (varName.empty()) return "";
#if (BOOST_OS_BSD || BOOST_OS_CYGWIN || BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_SOLARIS)
  char* value = std::getenv(varName.c_str());
  if (!value) return "";
  return value;
#elif (BOOST_OS_WINDOWS)
  typedef std::vector<char> char_vector;
  typedef std::vector<char>::size_type size_type;
  char_vector value(8192, 0);
  size_type size = value.size();
  bool haveValue = false;
  bool shouldContinue = true;
  do
  {
    DWORD result = GetEnvironmentVariableA(varName.c_str(), &value[0], size);
    if (result == 0)
    {
      shouldContinue = false;
    }
    else if (result < size)
    {
      haveValue = true;
      shouldContinue = false;
    }
    else
    {
      size *= 2;
      value.resize(size);
    }
  } while (shouldContinue);
  std::string ret;
  if (haveValue)
  {
    ret = &value[0];
  }
  return ret;
#else
  return "";
#endif
}

bool GetDirectoryListFromDelimitedString(
  const std::string& str,
  std::vector<std::string>& dirs)
{
  typedef boost::char_separator<char> char_separator_type;
  typedef boost::tokenizer<
    boost::char_separator<char>, std::string::const_iterator,
    std::string> tokenizer_type;
  dirs.clear();
  if (str.empty())
  {
    return false;
  }
#if (BOOST_OS_WINDOWS)
  const std::string os_pathsep(";");
#else
  const std::string os_pathsep(":");
#endif
  char_separator_type pathSep(os_pathsep.c_str());
  tokenizer_type strTok(str, pathSep);
  typename tokenizer_type::iterator strIt;
  typename tokenizer_type::iterator strEndIt = strTok.end();
  for (strIt = strTok.begin(); strIt != strEndIt; ++strIt)
  {
    dirs.push_back(*strIt);
  }
  if (dirs.empty())
  {
    return false;
  }
  return true;
}

std::string search_path(const std::string& file)
{
  if (file.empty()) return "";
  std::string ret;
#if (BOOST_VERSION > BOOST_VERSION_NUMBER(1,64,0))
  {
    namespace bp = boost::process;
    boost::filesystem::path p = bp::search_path(file);
    ret = p.make_preferred().string();
  }
#endif
  if (!ret.empty()) return ret;
  // Drat! I have to do it the hard way.
  std::string pathEnvVar = GetEnv("PATH");
  if (pathEnvVar.empty()) return "";
  std::vector<std::string> pathDirs;
  bool getDirList = GetDirectoryListFromDelimitedString(pathEnvVar, pathDirs);
  if (!getDirList) return "";
  std::vector<std::string>::const_iterator it = pathDirs.cbegin();
  std::vector<std::string>::const_iterator itEnd = pathDirs.cend();
  for ( ; it != itEnd; ++it)
  {
    boost::filesystem::path p(*it);
    p /= file;
    if (boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p))
    {
      return p.make_preferred().string();
    }
  }
  return "";
}

std::string executable_path_fallback(const char *argv0)
{
  if (argv0 == nullptr) return "";
  if (argv0[0] == 0) return "";
#if (BOOST_OS_WINDOWS)
  const std::string os_sep("\\");
#else
  const std::string os_sep("/");
#endif
  if (strstr(argv0, os_sep.c_str()) != nullptr)
  {
    boost::system::error_code ec;
    boost::filesystem::path p(
      boost::filesystem::canonical(
        argv0, boost::filesystem::current_path(), ec));
    if (ec.value() == boost::system::errc::success)
    {
      return p.make_preferred().string();
    }
  }
  std::string ret = search_path(argv0);
  if (!ret.empty())
  {
    return ret;
  }
  boost::system::error_code ec;
  boost::filesystem::path p(
    boost::filesystem::canonical(
      argv0, boost::filesystem::current_path(), ec));
  if (ec.value() == boost::system::errc::success)
  {
    ret = p.make_preferred().string();
  }
  return ret;
}

}
}

include / boost / executable_path.hpp

#ifndef BOOST_EXECUTABLE_PATH_HPP_
#define BOOST_EXECUTABLE_PATH_HPP_

#pragma once

#include <string>

namespace boost {
std::string executable_path(const char * argv0);
}

#endif // BOOST_EXECUTABLE_PATH_HPP_

include / boost / detail / executable_path_internals.hpp

#ifndef BOOST_DETAIL_EXECUTABLE_PATH_INTERNALS_HPP_
#define BOOST_DETAIL_EXECUTABLE_PATH_INTERNALS_HPP_

#pragma once

#include <string>
#include <vector>

namespace boost {
namespace detail {
std::string GetEnv(const std::string& varName);
bool GetDirectoryListFromDelimitedString(
    const std::string& str,
    std::vector<std::string>& dirs);
std::string search_path(const std::string& file);
std::string executable_path_fallback(const char * argv0);
}
}

#endif // BOOST_DETAIL_EXECUTABLE_PATH_INTERNALS_HPP_

Mam kompletny projekt, w tym aplikację testową i pliki kompilacji CMake dostępne w SnKOpen - / cpp / executable_path / trunk . Ta wersja jest bardziej kompletna niż wersja, którą tutaj podałem. Obsługuje również więcej platform.

Przetestowałem aplikację na wszystkich obsługiwanych systemach operacyjnych w następujących czterech scenariuszach.

  1. Ścieżka względna, plik wykonywalny w bieżącym katalogu: np. Test_ścieżki_wykonania
  2. Ścieżka względna, plik wykonywalny w innym katalogu: np. ./Build/executable_path_test
  3. Pełna ścieżka: tj. / Jakiś / katalog / test_ścieżki_wykonalnej
  4. Plik wykonywalny w ścieżce, tylko nazwa pliku: tj. Test_ścieżki_wykonalnej

We wszystkich czterech scenariuszach funkcje executable_path i executable_path_fallback działają i zwracają te same wyniki.

Uwagi

To jest zaktualizowana odpowiedź na to pytanie. Zaktualizowałem odpowiedź, aby uwzględnić komentarze i sugestie użytkowników. Dodałem również link do projektu w moim repozytorium SVN.


1
Wygląda to na bardzo kompletne rozwiązanie z rozsądnymi wadami. +1! Jedno pytanie: czy miałoby sens zastąpienie stałych buforów char [1024] czymś w rodzaju wektora <char>, którego rozmiar można zmienić, jeśli ścieżka przekracza rozmiar początkowy?
Daniel Wolf

Tak. To doskonała sugestia. Oczywiście należałoby wprowadzić dodatkowe zmiany, takie jak sprawdzenie błędów, zmiana rozmiaru bufora i ponowna próba.
Ben Key

1
Myślę, że rozwiązanie awaryjne nie jest poprawne. argv[0]może być również nazwą pliku wykonywalnego, w którym to przypadku konieczne byłoby wyszukanie go w PATHsystemach * nix.
Michał Górny

1
Próbowałem tego użyć. ale potrzebuje wzmocnienia, prawda? Myślałem, że to samodzielny
manatttta

1
Miałeś mnie na „boost :: dll :: program_location”
Thomas

31

W ten sposób używa boost + argv. Wspomniałeś, że to może nie być wieloplatformowe, ponieważ może, ale nie musi, zawierać nazwę pliku wykonywalnego. Poniższy kod powinien to obejść.

#include <boost/filesystem/operations.hpp>

#include <boost/filesystem/path.hpp>

#include <iostream>

namespace fs = boost::filesystem;


int main(int argc,char** argv)
{
    fs::path full_path( fs::initial_path<fs::path>() );

    full_path = fs::system_complete( fs::path( argv[0] ) );

    std::cout << full_path << std::endl;

    //Without file name
    std::cout << full_path.stem() << std::endl;
    //std::cout << fs::basename(full_path) << std::endl;

    return 0;
}

Poniższy kod pobiera bieżący katalog roboczy, który może zrobić to, czego potrzebujesz

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>

#include <iostream>

namespace fs = boost::filesystem;


int main(int argc,char** argv)
{
    //current working directory
    fs::path full_path( fs::current_path<fs::path>() );

    std::cout << full_path << std::endl;

    std::cout << full_path.stem() << std::endl;
    //std::cout << fs::basepath(full_path) << std::endl;

    return 0;
}

Uwaga Właśnie zdałem sobie sprawę, że basename() jest przestarzały, więc musiałem przejść na.stem()


rdzeń wydaje mi się podawać tylko plik wykonywalny bez ścieżki i rozszerzenia w systemie Windows, ale to drobna uwaga. Chciałbym wiedzieć, jak to działa, jeśli argument argv [0] jest prawdopodobnie niepoprawny? To działa dla mnie podczas testowania w systemie Windows, ale potem argv [0] jest faktycznie przekazywana jako absolutna ścieżka do pliku wykonywalnego, co sprawia, że ​​praca system_complete jest całkiem łatwa :)
Ben Hymers

1
Nie - nie potrzebuje katalogu roboczego. i NO argv nie pomaga. Co robisz, gdy argv zawiera tylko nazwę pliku wykonywalnego? Co zrobić, gdy program został wywołany przez łącze symboliczne?
Ichthyo

4
„// Bez nazwy pliku” - chcesz .parent_path(), nie .stem(), nie?
Claudiu

2
To chyba nie działa na mojej platformie (macOS El Capitan). Zamiast tego otrzymuję bieżący katalog roboczy. Ponadto, jak @Claudiupowiedziałem, myślę, że powinno .parent_path().
samvv

20

Nie jestem pewien co do Linuksa, ale wypróbuj to dla Windows:

#include <windows.h>
#include <iostream>

using namespace std ;

int main()
{
     char ownPth[MAX_PATH]; 

     // When NULL is passed to GetModuleHandle, the handle of the exe itself is returned
     HMODULE hModule = GetModuleHandle(NULL);
     if (hModule != NULL)
     {
         // Use GetModuleFileName() with module handle to get the path
         GetModuleFileName(hModule, ownPth, (sizeof(ownPth))); 
         cout << ownPth << endl ;
         system("PAUSE");
         return 0;
     }
     else
     {
         cout << "Module handle is NULL" << endl ;
         system("PAUSE");
         return 0;
     }
}

3
Zauważ, że należy użyć WCHAR ownPth..owiniętego wokół a #ifdef UNICODEw przypadku kompilacji z obsługą Unicode. Jeśli nie, użyj podanego kodu.
Dr1Ku

1
Tak dla przypomnienia, mam po prostu zabawny przypadek, w którym GetModuleDirectory zwraca ścieżkę z częściami „..”, tak jakby pobierał czysty ciąg znaków z wiersza poleceń lol. w rzeczywistości w tym przypadku Visual Studio uruchamia proces i .. jest częścią ścieżki debugowania. coś w rodzaju $ (projectDir) ../ some.exe Użyłem PathCanonicalize z Shwlib, ale trzeba połączyć się z tą biblioteką. może to nie być pożądane.
v.oddou

1
Zalecałbym również użycie TCHAR dla ownPath zamiast char. Ale i tak miła odpowiedź.
anhoppe

Czy to w ogóle możliwe, żeby się nie udało? Na pierwszy rzut oka wydaje się to mało prawdopodobne ...HMODULE hModule = GetModuleHandle(NULL);
kayleeFrye_onDeck

1
Jeśli pierwszy parametr GetModuleFileName ma wartość NULL, pobiera ścieżkę do pliku wykonywalnego bieżącego procesu.
lsalamon

12

Dla Windowsa:

GetModuleFileName - zwraca ścieżkę do exe + nazwę pliku exe

Aby usunąć nazwę pliku
PathRemoveFileSpec


1
Dokumentów pamiętać o PathRemoveFileSpec: This function is deprecated. We recommend the use of the PathCchRemoveFileSpec function in its place.
javs

12

C ++ 17, windows, unicode, przy użyciu nowego interfejsu API systemu plików:

#include "..\Project.h"
#include <filesystem>
using namespace std;
using namespace filesystem;

int wmain(int argc, wchar_t** argv)
{
    auto dir = weakly_canonical(path(argv[0])).parent_path();
    printf("%S", dir.c_str());
    return 0;
}

Podejrzewam, że to rozwiązanie powinno być przenośne, ale nie wiem, jak unicode jest zaimplementowany w innych systemach operacyjnych.

slave_canonical jest potrzebny tylko wtedy, gdy używasz jako odniesień do górnego folderu katalogu wyjściowego („..”) w celu uproszczenia ścieżki. Jeśli go nie używasz - usuń.

Jeśli korzystasz z biblioteki dołączanej dynamicznie (.dll /.so), możesz nie mieć argv, możesz rozważyć następujące rozwiązanie:

application.h:

#pragma once

//
// https://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros
//
#ifdef __cpp_lib_filesystem
#include <filesystem>
#else
#include <experimental/filesystem>

namespace std {
    namespace filesystem = experimental::filesystem;
}
#endif

std::filesystem::path getexepath();

application.cpp:

#include "application.h"
#ifdef _WIN32
#include <windows.h>    //GetModuleFileNameW
#else
#include <limits.h>
#include <unistd.h>     //readlink
#endif

std::filesystem::path getexepath()
{
#ifdef _WIN32
    wchar_t path[MAX_PATH] = { 0 };
    GetModuleFileNameW(NULL, path, MAX_PATH);
    return path;
#else
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
#endif
}

Strażnicy w nagłówku nie są właściwym testowaniem na obecność systemu plików. cppreference pokazuje, że wartość makra testu funkcji jest zdefiniowana w samym nagłówku systemu plików, dlatego testowanie przed włączeniem nie działa. __has_include () jest tutaj lepszym standardowym testem.
Meteorhead

8

QT zapewnia to z abstrakcją systemu operacyjnego jako QCoreApplication :: applicationDirPath ()


Pierwsze z tym: QCoreApplication::applicationDirPath: Please instantiate the QApplication object first. Masz jakiś pomysł, jak to rozwiązać?
GuySoft

@GuySoft: Po prostu utwórz instancję QCoreApplicationpodobną QApplication application(argc, argv);(zrób to w swoim main(argc, argv)i upewnij się, że nie modyfikujesz argc/argv, ponieważ muszą one pozostać ważne przez cały okres eksploatacji aplikacji QCore (sprawdź dokumentację )
ted

5

Jest to sposób specyficzny dla systemu Windows, ale to przynajmniej połowa odpowiedzi.

GetThisPath.h

/// dest is expected to be MAX_PATH in length.
/// returns dest
///     TCHAR dest[MAX_PATH];
///     GetThisPath(dest, MAX_PATH);
TCHAR* GetThisPath(TCHAR* dest, size_t destSize);

GetThisPath.cpp

#include <Shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

TCHAR* GetThisPath(TCHAR* dest, size_t destSize)
{
    if (!dest) return NULL;
    if (MAX_PATH > destSize) return NULL;

    DWORD length = GetModuleFileName( NULL, dest, destSize );
    PathRemoveFileSpec(dest);
    return dest;
}

mainProgram.cpp

TCHAR dest[MAX_PATH];
GetThisPath(dest, MAX_PATH);

Sugerowałbym użycie wykrywania platformy jako dyrektyw preprocesora, aby zmienić implementację funkcji opakowującej, która wywołuje GetThisPathkażdą platformę.


3

Używając argumentów [0] i szukając „/” (lub „\\”):

#include <string>
#include <iostream> // to show the result

int main( int numArgs, char *args[])
{
    // Get the last position of '/'
    std::string aux(args[0]);

    // get '/' or '\\' depending on unix/mac or windows.
#if defined(_WIN32) || defined(WIN32)
    int pos = aux.rfind('\\');
#else
    int pos = aux.rfind('/');
#endif

    // Get the path and the name
    std::string path = aux.substr(0,pos+1);
    std::string name = aux.substr(pos+1);
    // show results
    std::cout << "Path: " << path << std::endl;
    std::cout << "Name: " << name << std::endl;
}

EDYTOWANE: Jeśli „/” nie istnieje, pos == - 1, więc wynik jest poprawny.


A jeśli na ścieżce nie ma znaku „/”? Nie ma sprawdzania tego przypadku i uważam, że jest to całkiem prawdopodobne - system Windows użyje odwrotnego ukośnika i args[0]może wcale nie być ścieżką.
Ben Hymers,

Jeśli '/' nie istnieje, rfind zwraca -1, więc "path" = aux.substr (0,0) i "name" = aux.substr (0): wynik jest poprawny. Jeśli chodzi o Windows, masz rację, „/” należy zmienić na „\\”, zmienię, aby zezwolić na okna. Testowałem również pod kątem nazw plików z „/”, ale ten ostatni jest skodyfikowany i nie stwarza problemów.
Adrian Maire,

1
Bardziej args[0]przeszkadza mi to, że niekoniecznie jest to ścieżka wykonywalna. Dzięki za poprawienie odpowiedzi dla systemu Windows :)
Ben Hymers,

1
Jeśli polecenie zostanie uruchomione bez podania ścieżki (tzn. Zostanie znalezione w katalogu podanym w PATH env var), args [0] będzie po prostu nazwą pliku wykonywalnego, bez ścieżki.
Kevin

@Kevin: ty (i inni) masz rację, jest to proste rozwiązanie dla małych narzędzi, które działają ~ 95% przypadków. W przypadku poważnego oprogramowania plik konfiguracyjny i / lub zmienna środowiskowa są prawdopodobnie lepsze. Ponadto potrzeba ta zwykle oznacza niezbyt dobry (lub nawet zły) projekt.
Adrian Maire


1

Poniższe rozwiązanie działa jako szybkie i brudne rozwiązanie, ale pamiętaj, że nie jest ono niezawodne:

#include <iostream>

using namespace std ;

int main( int argc, char** argv)
{
    cout << argv[0] << endl ;
    return 0;
}

17
Widziałem przy innych pytaniach SO, że to nie zawsze działa i że argv [0] może zawierać bezwzględną ścieżkę do pliku wykonywalnego, tylko nazwę pliku wykonywalnego lub inne śmieci.
Ben Hymers

7
Nie należy nigdy ufać argv [0], jeśli próbuje otworzyć „pliki pomocnicze” lub tym podobne. Argv podlega zmianom, a każdy zły rozmówca może zmienić wartość tego. Unikaj, chyba że używasz go do logowania itp., NIE do tworzenia ścieżek używanych do otwierania plików.
Qix - MONICA ZOSTAŁA POMYŚLNA

to nie działa w systemie Windows. argv [0] nie będzie mieć pełnej ścieżki. Tylko plik .exe. Proszę, nie próbuj w powłoce bash, wypróbuj ją w tej standardowej konsoli i cout << argv [0], aby odtworzyć.
Freddy Martinez Garcia

@FreddyMartinezGarcia Cóż, przetestowałbym to w systemie Windows, więc YMMV. To wszystko, co zostało użyte do uruchomienia kodu. Jeśli masz plik wykonywalny w CWD, na pewno otrzymasz tylko nazwę pliku.
Clifford

0

W przypadku konieczności obsługi ścieżek Unicode w systemie Windows:

#include <Windows.h>
#include <iostream>

int wmain(int argc, wchar_t * argv[])
{
    HMODULE this_process_handle = GetModuleHandle(NULL);
    wchar_t this_process_path[MAX_PATH];

    GetModuleFileNameW(NULL, this_process_path, sizeof(this_process_path));

    std::wcout << "Unicode path of this app: " << this_process_path << std::endl;

    return 0;
}

0

W systemie Windows masz problem, jak usunąć plik wykonywalny z wyniku GetModuleFileName(). Wywołanie Windows API, PathRemoveFileSpec()którego Nate użył w tym celu w swojej odpowiedzi, zmieniło się między Windows 8 a jego poprzednikami. Jak więc pozostać kompatybilnym i bezpiecznym? Na szczęście jest C ++ 17 (lub Boost, jeśli używasz starszego kompilatora). Robię to:

#include <windows.h>
#include <string>
#include <filesystem>
namespace fs = std::experimental::filesystem;

// We could use fs::path as return type, but if you're not aware of
// std::experimental::filesystem, you probably handle filenames
// as strings anyway in the remainder of your code.  I'm on Japanese
// Windows, so wide chars are a must.
std::wstring getDirectoryWithCurrentExecutable()
{
    int size = 256;
    std::vector<wchar_t> charBuffer;
    // Let's be safe, and find the right buffer size programmatically.
    do {
        size *= 2;
        charBuffer.resize(size);
        // Resize until filename fits.  GetModuleFileNameW returns the
        // number of characters written to the buffer, so if the
        // return value is smaller than the size of the buffer, it was
        // large enough.
    } while (GetModuleFileNameW(NULL, charBuffer.data(), size) == size);
    // Typically: c:/program files (x86)/something/foo/bar/exe/files/win64/baz.exe
    // (Note that windows supports forward and backward slashes as path
    // separators, so you have to be careful when searching through a path
    // manually.)

    // Let's extract the interesting part:
    fs::path path(charBuffer.data());  // Contains the full path including .exe
    return path.remove_filename()  // Extract the directory ...
               .w_str();           // ... and convert to a string.
}

0

Jak wspominali inni, argv[0]jest to całkiem fajne rozwiązanie, pod warunkiem, że platforma faktycznie przechodzi ścieżkę do pliku wykonywalnego, co z pewnością jest nie mniej prawdopodobne niż w przypadku systemu operacyjnego Windows (gdzie WinAPI może pomóc w znalezieniu ścieżki do pliku wykonywalnego). Jeśli chcesz usunąć ciąg, aby uwzględnić tylko ścieżkę do katalogu, w którym znajduje się plik wykonywalny, użycie tej ścieżki do znalezienia innych plików aplikacji (takich jak zasoby gry, jeśli program jest grą) jest całkowicie w porządku, ponieważ otwieranie plików jest względne katalog roboczy lub, jeśli jest dostępny, katalog główny.


0

Na tym skończyłem

Plik nagłówkowy wygląda następująco:

#pragma once

#include <string>
namespace MyPaths {

  std::string getExecutablePath();
  std::string getExecutableDir();
  std::string mergePaths(std::string pathA, std::string pathB);
  bool checkIfFileExists (const std::string& filePath);

}

Realizacja


#if defined(_WIN32)
    #include <windows.h>
    #include <Shlwapi.h>
    #include <io.h> 

    #define access _access_s
#endif

#ifdef __APPLE__
    #include <libgen.h>
    #include <limits.h>
    #include <mach-o/dyld.h>
    #include <unistd.h>
#endif

#ifdef __linux__
    #include <limits.h>
    #include <libgen.h>
    #include <unistd.h>

    #if defined(__sun)
        #define PROC_SELF_EXE "/proc/self/path/a.out"
    #else
        #define PROC_SELF_EXE "/proc/self/exe"
    #endif

#endif

namespace MyPaths {

#if defined(_WIN32)

std::string getExecutablePath() {
   char rawPathName[MAX_PATH];
   GetModuleFileNameA(NULL, rawPathName, MAX_PATH);
   return std::string(rawPathName);
}

std::string getExecutableDir() {
    std::string executablePath = getExecutablePath();
    char* exePath = new char[executablePath.length()];
    strcpy(exePath, executablePath.c_str());
    PathRemoveFileSpecA(exePath);
    std::string directory = std::string(exePath);
    delete[] exePath;
    return directory;
}

std::string mergePaths(std::string pathA, std::string pathB) {
  char combined[MAX_PATH];
  PathCombineA(combined, pathA.c_str(), pathB.c_str());
  std::string mergedPath(combined);
  return mergedPath;
}

#endif

#ifdef __linux__

std::string getExecutablePath() {
   char rawPathName[PATH_MAX];
   realpath(PROC_SELF_EXE, rawPathName);
   return  std::string(rawPathName);
}

std::string getExecutableDir() {
    std::string executablePath = getExecutablePath();
    char *executablePathStr = new char[executablePath.length() + 1];
    strcpy(executablePathStr, executablePath.c_str());
    char* executableDir = dirname(executablePathStr);
    delete [] executablePathStr;
    return std::string(executableDir);
}

std::string mergePaths(std::string pathA, std::string pathB) {
  return pathA+"/"+pathB;
}

#endif

#ifdef __APPLE__
    std::string getExecutablePath() {
        char rawPathName[PATH_MAX];
        char realPathName[PATH_MAX];
        uint32_t rawPathSize = (uint32_t)sizeof(rawPathName);

        if(!_NSGetExecutablePath(rawPathName, &rawPathSize)) {
            realpath(rawPathName, realPathName);
        }
        return  std::string(realPathName);
    }

    std::string getExecutableDir() {
        std::string executablePath = getExecutablePath();
        char *executablePathStr = new char[executablePath.length() + 1];
        strcpy(executablePathStr, executablePath.c_str());
        char* executableDir = dirname(executablePathStr);
        delete [] executablePathStr;
        return std::string(executableDir);
    }

    std::string mergePaths(std::string pathA, std::string pathB) {
        return pathA+"/"+pathB;
    }
#endif


bool checkIfFileExists (const std::string& filePath) {
   return access( filePath.c_str(), 0 ) == 0;
}

}

0

Biblioteka SDL2 ( https://www.libsdl.org/ ) posiada dwie funkcje zaimplementowane na szerokim spektrum platform:

  • SDL_GetBasePath
  • SDL_GetPrefPath

Więc jeśli nie chcesz wymyślać koła na nowo ... niestety oznacza to włączenie całej biblioteki, chociaż ma dość liberalną licencję i można też po prostu skopiować kod. Poza tym zapewnia wiele innych funkcji międzyplatformowych.


0

Jest to prawdopodobnie najbardziej naturalny sposób na zrobienie tego, obejmujący większość głównych platform stacjonarnych. Nie jestem pewien, ale uważam, że powinno to działać ze wszystkimi BSD, a nie tylko z FreeBSD, jeśli zmienisz sprawdzanie makr platformy tak, aby obejmowało je wszystkie. Jeśli kiedykolwiek przejdę do instalacji Solaris, na pewno dodam tę platformę do listy obsługiwanych.

Zawiera pełną obsługę UTF-8 w systemie Windows, co nie wszystkim zależy na tym, aby posunąć się tak daleko.

procinfo / win32 / procinfo.cpp

#ifdef _WIN32
#include "../procinfo.h"
#include <windows.h>
#include <tlhelp32.h>
#include <cstddef>
#include <vector>
#include <cwchar>

using std::string;
using std::wstring;
using std::vector;
using std::size_t;

static inline string narrow(wstring wstr) {
  int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
  vector<char> buf(nbytes);
  return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
}

process_t ppid_from_pid(process_t pid) {        
  process_t ppid;       
  HANDLE hp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);      
  PROCESSENTRY32 pe = { 0 };        
  pe.dwSize = sizeof(PROCESSENTRY32);       
  if (Process32First(hp, &pe)) {        
    do {        
      if (pe.th32ProcessID == pid) {        
        ppid = pe.th32ParentProcessID;      
        break;      
      }     
    } while (Process32Next(hp, &pe));       
  }     
  CloseHandle(hp);      
  return ppid;      
}

string path_from_pid(process_t pid) {
  string path;
  HANDLE hm = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
  MODULEENTRY32W me = { 0 };
  me.dwSize = sizeof(MODULEENTRY32W);
  if (Module32FirstW(hm, &me)) {
    do {
      if (me.th32ProcessID == pid) {
        path = narrow(me.szExePath);
        break;
      }
    } while (Module32NextW(hm, &me));
  }
  CloseHandle(hm);
  return path;
}
#endif

procinfo / macosx / procinfo.cpp

#if defined(__APPLE__) && defined(__MACH__)
#include "../procinfo.h"
#include <libproc.h>

using std::string;

string path_from_pid(process_t pid) {
  string path;
  char buffer[PROC_PIDPATHINFO_MAXSIZE];
  if (proc_pidpath(pid, buffer, sizeof(buffer)) > 0) {
    path = string(buffer) + "\0";
  }
  return path;
}
#endif

procinfo / linux / procinfo.cpp

#ifdef __linux__
#include "../procinfo.h"
#include <cstdlib>

using std::string;
using std::to_string;

string path_from_pid(process_t pid) {
  string path;
  string link = string("/proc/") + to_string(pid) + string("/exe");
  char *buffer = realpath(link.c_str(), NULL);
  path = buffer ? : "";
  free(buffer);
  return path;
}
#endif

procinfo / freebsd / procinfo.cpp

#ifdef __FreeBSD__
#include "../procinfo.h"
#include <sys/sysctl.h>
#include <cstddef>

using std::string;
using std::size_t;

string path_from_pid(process_t pid) {
  string path;
  size_t length;
  // CTL_KERN::KERN_PROC::KERN_PROC_PATHNAME(pid)
  int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid };
  if (sysctl(mib, 4, NULL, &length, NULL, 0) == 0) {
    path.resize(length, '\0');
    char *buffer = path.data();
    if (sysctl(mib, 4, buffer, &length, NULL, 0) == 0) {
      path = string(buffer) + "\0";
    }
  }
  return path;
}
#endif

procinfo / procinfo.cpp

#include "procinfo.h"
#ifdef _WiN32
#include <process.h>
#endif
#include <unistd.h>
#include <cstddef>

using std::string;
using std::size_t;

process_t pid_from_self() {
  #ifdef _WIN32
  return _getpid();
  #else
  return getpid();
  #endif
}

process_t ppid_from_self() {
  #ifdef _WIN32
  return ppid_from_pid(pid_from_self());
  #else
  return getppid();
  #endif
}

string dir_from_pid(process_t pid) {
  string fname = path_from_pid(pid);
  size_t fp = fname.find_last_of("/\\");
  return fname.substr(0, fp + 1);
}

string name_from_pid(process_t pid) {
  string fname = path_from_pid(pid);
  size_t fp = fname.find_last_of("/\\");
  return fname.substr(fp + 1);
}

procinfo / procinfo.h

#ifdef _WiN32
#include <windows.h>
typedef DWORD process_t;
#else
#include <sys/types.h>
typedef pid_t process_t;
#endif
#include <string>

/* windows-only helper function */
process_t ppid_from_pid(process_t pid);

/* get current process process id */
process_t pid_from_self();

/* get parent process process id */
process_t ppid_from_self();

/* std::string possible_result = "C:\\path\\to\\file.exe"; */
std::string path_from_pid(process_t pid);

/* std::string possible_result = "C:\\path\\to\\"; */
std::string dir_from_pid(process_t pid);

/* std::string possible_result = "file.exe"; */
std::string name_from_pid(process_t pid);

Pozwala to na uzyskanie pełnej ścieżki do pliku wykonywalnego prawie dowolnego identyfikatora procesu, z wyjątkiem tego, że w systemie Windows są pewne procesy z atrybutami bezpieczeństwa, które po prostu na to nie pozwalają, więc wysiwyg, to rozwiązanie nie jest idealne.

Aby dokładniej odpowiedzieć na pytanie, o które chodzi, możesz zrobić to:

procinfo.cpp

#include "procinfo/procinfo.h"
#include <iostream>

using std::string;
using std::cout;
using std::endl;

int main() {
  cout << dir_from_pid(pid_from_self()) << endl;
  return 0;
}

Zbuduj powyższą strukturę plików za pomocą tego polecenia:

procinfo.sh

cd "${0%/*}"
g++ procinfo.cpp procinfo/procinfo.cpp procinfo/win32/procinfo.cpp procinfo/macosx/procinfo.cpp procinfo/linux/procinfo.cpp procinfo/freebsd/procinfo.cpp -o procinfo.exe

Aby pobrać kopię plików wymienionych powyżej:

git clone git://github.com/time-killer-games/procinfo.git

Aby uzyskać więcej korzyści związanych z procesami międzyplatformowymi:

https://github.com/time-killer-games/enigma-dev

Zobacz plik readme, aby zapoznać się z listą większości zawartych funkcji.


0

Jeśli używasz C ++ 17, możesz wykonać następujące czynności, aby uzyskać ścieżkę do pliku wykonywalnego.

#include <filesystem>

std::filesystem::path getExecutablePath()
{
    return std::filesystem::canonical("/proc/self/exe");
}

Powyższa odpowiedź została przetestowana na Debianie 10 przy użyciu G ++ 9.3.0


Zauważ, że zadziała to tylko wtedy, gdy / proc / self / exe istnieje i jest dostępne. Powinieneś prawdopodobnie sprawdzić, czy tak jest.
Zrin

-1

Od C ++ 17:

Upewnij się, że dołączasz system plików std.

#include <filesystem>

a teraz możesz to zrobić.

std::filesystem::current_path().string()

boost stał się częścią standardowego lib.

jeśli nie możesz go znaleźć, poszukaj pod:

std::experimental::filesystem

10
To nie jest ścieżka pliku binarnego, to bieżący katalog roboczy.
Zitrax

-2

To było moje rozwiązanie w systemie Windows. Nazywa się to tak:

std::wstring sResult = GetPathOfEXE(64);

Gdzie 64 to minimalny rozmiar, który Twoim zdaniem będzie. GetPathOfEXE wywołuje siebie rekurencyjnie, podwajając rozmiar bufora za każdym razem, aż otrzyma wystarczająco duży bufor, aby uzyskać całą ścieżkę bez obcięcia.

std::wstring GetPathOfEXE(DWORD dwSize)
{
    WCHAR* pwcharFileNamePath;
    DWORD dwLastError;
    HRESULT hrError;
    std::wstring wsResult;
    DWORD dwCount;

    pwcharFileNamePath = new WCHAR[dwSize];

    dwCount = GetModuleFileNameW(
        NULL,
        pwcharFileNamePath,
        dwSize
    );

    dwLastError = GetLastError();

    if (ERROR_SUCCESS == dwLastError)
    {
        hrError = PathCchRemoveFileSpec(
            pwcharFileNamePath,
            dwCount
        );

        if (S_OK == hrError)
        {
            wsResult = pwcharFileNamePath;

            if (pwcharFileNamePath)
            {
                delete pwcharFileNamePath;
            }

            return wsResult;
        }
        else if(S_FALSE == hrError)
        {
            wsResult = pwcharFileNamePath;

            if (pwcharFileNamePath)
            {
                delete pwcharFileNamePath;
            }

            //there was nothing to truncate off the end of the path
            //returning something better than nothing in this case for the user
            return wsResult;
        }
        else
        {
            if (pwcharFileNamePath)
            {
                delete pwcharFileNamePath;
            }

            std::ostringstream oss;
            oss << "could not get file name and path of executing process. error truncating file name off path. last error : " << hrError;
            throw std::runtime_error(oss.str().c_str());
        }
    }
    else if (ERROR_INSUFFICIENT_BUFFER == dwLastError)
    {
        if (pwcharFileNamePath)
        {
            delete pwcharFileNamePath;
        }

        return GetPathOfEXE(
            dwSize * 2
        );
    }
    else
    {
        if (pwcharFileNamePath)
        {
            delete pwcharFileNamePath;
        }

        std::ostringstream oss;
        oss << "could not get file name and path of executing process. last error : " << dwLastError;
        throw std::runtime_error(oss.str().c_str());
    }
}

Jaki jest powód używania newi (źle) delete? Gdybyś użył a std::vector, twój kod nie wykazywałby niezdefiniowanego zachowania.
Niespodziewane

Poza tym GetModuleFileNameWnie ustawia ostatniego kodu błędu w przypadku sukcesu. Ten kod jest złamany na wiele sposobów. Nie używaj, jeśli się na to natkniesz.
Niespodziewane

-3
char exePath[512];
CString strexePath;
GetModuleFileName(NULL,exePath,512);
strexePath.Format("%s",exePath);
strexePath = strexePath.Mid(0,strexePath.ReverseFind('\\'));

2
To tylko Windows i używa MFC, więc bardzo daleko od bycia wieloplatformowym, przepraszam!
Ben Hymers,

1
To też nie jest sposób, w jaki Windows to robi. PathRemoveFileSpec()Zamiast tego spójrz na i powiązane funkcje.
Remy Lebeau

-4

w Uniksie (w tym Linuksie) spróbuj „który”, w Windows spróbuj „gdzie”.

#include <stdio.h>

#define _UNIX

int main(int argc, char** argv)
{
        char cmd[128];
        char buf[128];
        FILE* fp = NULL;
#if defined(_UNIX)
        sprintf(cmd, "which %s > my.path", argv[0]);
#else
        sprintf(cmd, "where %s > my.path", argv[0]);
#endif
        system(cmd);
        fp = fopen("my.path", "r");
        fgets(buf, sizeof(buf), fp);
        fclose(fp);

        printf("full path: %s\n", buf);
        unlink("my.path");

        return 0;
}

-4

Ta metoda działa zarówno w systemie Windows, jak i Linux:

#include <stdio.h>
#include <string>
#ifdef _WIN32
#include <direct.h>
#define GetCurrentDir _getcwd
#elif __linux__
#include <unistd.h>
#define GetCurrentDir getcwd
#endif

std::string GetCurrentWorkingDir() 
{
    char buff[FILENAME_MAX];
    GetCurrentDir(buff, FILENAME_MAX);
    std::string current_working_dir(buff);
    return current_working_dir;
}

2
Zwraca bieżący katalog roboczy, a nie ścieżkę do pliku wykonywalnego, który może nie być tym samym.
Dave Durbin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.