Solución:
No hay una forma multiplataforma que yo sepa.
Para Linux: readlink / proc / self / exe
Windows: GetModuleFileName
La función boost :: dll :: program_location es uno de los mejores métodos multiplataforma para obtener la ruta del ejecutable en ejecución que conozco. La biblioteca DLL se agregó a Boost en la versión 1.61.0.
La siguiente es mi solución. Lo he probado en Windows, Mac OS X, Solaris, Free BSD y GNU / Linux.
Requiere Boost 1.55.0 o superior. Utiliza la biblioteca Boost.Filesystem directamente y la biblioteca Boost.Locale y la biblioteca Boost.System indirectamente.
src / ruta_ejecutable.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 / ruta_ejecutable_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("https://foroayuda.es/");
#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;
}
}
}
incluir / boost / ruta_ejecutable.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_
incluir / boost / detail / ruta_ejecutable_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_
Tengo un proyecto completo, que incluye una aplicación de prueba y archivos de compilación de CMake disponibles en SnKOpen – / cpp / ejecutable_path / trunk. Esta versión es más completa que la versión que proporcioné aquí. También es compatible con más plataformas.
He probado la aplicación en todos los sistemas operativos compatibles en los siguientes cuatro escenarios.
- Ruta relativa, ejecutable en el directorio actual: es decir ./executable_path_test
- Ruta relativa, ejecutable en otro directorio: es decir, ./build/executable_path_test
- Ruta completa: es decir, / algunos / dir / ejecutable_path_test
- Ejecutable en la ruta, solo el nombre del archivo: es decir, ejecutable_ruta_prueba
En los cuatro escenarios, las funciones ejecutable_path y ejecutable_path_fallback funcionan y devuelven los mismos resultados.
Notas
Ésta es una respuesta actualizada a esta pregunta. Actualicé la respuesta para tener en cuenta los comentarios y sugerencias de los usuarios. También agregué un enlace a un proyecto en mi Repositorio SVN.
De esta manera usa boost + argv. Mencionaste que esto puede no ser multiplataforma porque puede o no incluir el nombre del ejecutable. Bueno, el siguiente código debería solucionarlo.
#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;
}
El siguiente código obtiene el directorio de trabajo actual que puede hacer lo que necesita
#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;
}
Nota Acabo de darme cuenta de que basename(
) estaba en desuso, por lo que tuve que cambiar a .stem()