LINUX.ORG.RU

C++ как получить путь запущенного приложения?

 , ,


0

1

использую c++17 c++20
имеется приложение, которое использует ресурсы используя относительные пути — как выяснить реальный путь в файловой системе до самого бинаря внутри кода?

апд
в общем найдено несколько способов:
1. только для систем с procfs — C++ как получить путь запущенного приложения? (комментарий)
2. по идее кроссплатформенный через sdl2 — C++ как получить путь запущенного приложения? (комментарий)
3. использовать метод нахождения используемый в which (определять является ли argv путем, если да то каким — относительным или абсолютным, если нет, то искать название бинарника по путям в env var PATH)

★★

Последнее исправление: safocl (всего исправлений: 2)
Ответ на: комментарий от safocl

можно собрать линукс дистр без пакетного менеджера

Ты для себя можешь что угодно делать. А пользователи системы ставят софт из пакетов. И только от софта из родного репозитория можно ожидать нормальной работы. Никак не от принесённого неизветстно откуда неизвестно как собранного бинарника.

Про «не должно» енто ваще лютейший бред — в каждой проге чо ли надо делать один и тот же код?

Не должно не потому что мне так хочется, а потому что это невозможно.

с чего ты взял чо речь вообще идет о каком то коде который запускается из файла? — я вроде бы ясно и недвусмысленно указал, что ресурсы — подобные аудиофайлам, видеофайлам, картинкам и т.д.

При чём тут ресурсы? Я о бинарнике.

а если у тебя портативная версия — где ты будешь искать ресы если не относительно бинарника?

У меня никогда не будет никакой портативной версии. Весь мой софт штатно ставится в систему и ищет ресурсы по абсолютным путям.

slovazap ★★★★★
()
Ответ на: комментарий от safocl

Ты собрался вычислять путь до ресурсов относительно бинарника. Я говорю что может не быть такого пути, да и бинарника может не быть. Поэтому в кросс-платформенных библиотеках не может быть методов связанных с этим.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Поэтому в кросс-платформенных библиотеках не может быть методов связанных с этим.

ну так есть жеж — в sdl2 как минимум

и как понять чо бинарника может не быть?? будет жеж в любом случае какая то запускная точка...

safocl ★★
() автор топика
Последнее исправление: safocl (всего исправлений: 1)

Я смотрел, как реализовано в Qt и делал похоже, немного более упрощенно.

Linux: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qcoreapplication.cpp.html#2349

Windows: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qcoreapplication_win.cpp.html#_Z12qAppFileNamev

Я у себя делал так:

#if defined HOST_SYSTEM_LINUX

#include <unistd.h>
#include <filesystem>

static std::filesystem::path applicationFilePathLinux() {
    using namespace std::filesystem;
    using namespace std;
    return canonical(path("/proc") / path(to_string(getpid())) / path("exe"));
}

static std::filesystem::path applicationFileDirectoryLinux() { return applicationFilePathLinux().parent_path(); }

std::filesystem::path applicationFilePath() { return applicationFilePathLinux(); }
std::filesystem::path applicationDirectoryPath() { return applicationFileDirectoryLinux(); }

#elif defined HOST_SYSTEM_WINDOWS

#include <algorithm>
#include <filesystem>
#include <string>
#include <vector>

#include <windows.h>

static std::filesystem::path applicationFilePathWindows() {

    std::vector<wchar_t> readBuffer(MAX_PATH + 1);
    unsigned long readedCount;
    readedCount = GetModuleFileName(nullptr, readBuffer.data(), readBuffer.size());

    if (readBuffer.size() < readedCount) {
        throw std::runtime_error("applicationFilePathWindows() Error name exceeds buffer size.");
    }

    std::wstring executablePath;
    std::copy(readBuffer.begin(), readBuffer.end(), std::back_inserter(executablePath));
    return std::filesystem::path(executablePath);
}
static std::filesystem::path applicationDirectoryPathWindows() { return applicationFilePathWindows().parent_path(); }
std::filesystem::path applicationFilePath() { return applicationFilePathWindows(); }
std::filesystem::path applicationDirectoryPath() { return applicationDirectoryPathWindows(); }

#endif

Для Windows нужно определить символы препроцессора _UNICODE и UNICODE. Также в зависимости от системы нужно определить символы препроцессора HOST_SYSTEM_LINUX или HOST_SYSTEM_WINDOWS (я это делаю через CMake).

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 3)
Ответ на: комментарий от safocl

кстати нашел один кроссплатформенный способ — через SDL2/SDL_filesystem.h — функция SDL_GetBasePath()

Ха, посмотри, как она реализована:

https://hg.libsdl.org/SDL/file/8d826bc39a45/src/filesystem/unix/SDL_sysfilesystem.c#l78

retval = readSymLink("/proc/self/exe");  /* linux. */
rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 2)
Ответ на: комментарий от slovazap

У меня никогда не будет никакой портативной версии. Весь мой софт штатно ставится в систему и ищет ресурсы по абсолютным путям.

Да, да, да. Это все так. Но иногда нужно. Может какую-нибудь утилиту-помощник для портабельного запуска или хотя бы вирус или еще что-то. Не суть. Но иногда требуется. Пусть и редко.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

понятно — но ентот код не надо писать самому в каждом приложении — в ентом и удобство чо енто находится в кроссплатформенной либе.

safocl ★★
() автор топика
Ответ на: комментарий от safocl

ну так есть жеж — в sdl2 как минимум

А почему ты решил что sdl2 переносима? Потом, загляни в код и посмотри в каком безумном количестве случаев она возвращает nullptr, SDL_Unsupported() и что вообще возвращает во всяких emscripten/nacl.

slovazap ★★★★★
()
Ответ на: комментарий от safocl

Ну бывает, тебе либу тянуть - слишком жирно.

rumgot ★★★★★
()
Ответ на: комментарий от X512

Кстати, в перле проще:

#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;

$0 = "anus";
sleep 100;
$ ./test.pl &
[1] 13664

$ ps 13664
    PID TTY      STAT   TIME COMMAND
  13664 pts/0    S      0:00 anus
Binkledum
()

У меня в проекте две реализации (Windows/Linux):

Path getApplicationFilePath() {
#ifdef _WIN32
    wchar_t pathStr[MAX_PATH];
    const DWORD length = ::GetModuleFileNameW(NULL, pathStr, MAX_PATH);
    if (length == 0) {
        const DWORD lastError = ::GetLastError();
        const std::error_code ec{static_cast<int>(lastError), boost::system::system_category()};
        throw Exception{ec, "getApplicationFilePath(): Failed ::GetModuleFileNameW"};
    };
#else
    char pathStr[PATH_MAX];
    const ssize_t length = ::readlink("/proc/self/exe", pathStr, PATH_MAX);
    if (length < 0) {
        const std::error_code ec{errno, boost::system::generic_category()};
        throw Exception{ec, "getApplicationFilePath(): Failed ::readlink(\"/proc/self/exe\"..."};
    };
#endif
    return Path{std::begin(pathStr), std::begin(pathStr) + length};
}
dvetutnev
()
Ответ на: комментарий от dvetutnev

Path{std::begin(pathStr), std::begin(pathStr) + length};

В чем сакральный смысл так писать, если можно просто:

Path{pathStr, pathStr + length};

Или это C++ головного мозга?

anonymous
()
Ответ на: комментарий от anonymous

Вот тебе еще нарыл: getauxval(AT_EXECFN), чтобы не ипать мозг с выделением буфера под неизвестную длину в readlink. Или даже getauxval(AT_EXECFD) вместе с openat.

Любопытное низкоуровневое решение. Открываются интересные перспективы для оптимизации. Хоть я когда-то и читал эту доку, но абсолютно ничего из того, что ты написал я не запомнил и не знал. Спасибо за пост, один из лучших в этом треде.

anonymous
()

Во насоветовали. :)

Если совсем упороться на предмет абсолютной переносимости, то можно фигачить ресурсы прямо в бинарник в отдельную секцию, или даже просто в хвост бинарника да и всё. Можно прям в виде образа какой-нибудь простенькой ФС, тогда юзер сможет их изменять без проблем, будет вполне юзер-френдли. Метод далеко не нов и, как ни странно, достаточно часто используется как в венде, так и в линуксе (как минимум в Qt), хоть и не в юзер-френдли виде.

Stanson ★★★★★
()
Последнее исправление: Stanson (всего исправлений: 1)
Ответ на: комментарий от anonymous

В чем сакральный смысл так писать, если можно просто:

Path{pathStr, pathStr + length};

А если в pathStr будет не char/wchar_t, а std::string/std::wstring?

Понятно, что в приведенном коде там простые типы, но это типа как более универсально, например захочет он std::string, и ту строку уже переписывать не нужно.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 2)
Ответ на: комментарий от safocl

я просто изначально думал чо в std::filesystem реализован механизм обнаружения пути к exe — но увы — нет…

Вообще конечно такую элементарщину могли бы и включить в стандарт, тем более в разных либах этот функционал реализован плюс-минус одинаково.

rumgot ★★★★★
()
Ответ на: комментарий от seiken

Ну почему бы и нет. Вот как текущий путь, так есть возможность стандартной функцией получить. А путь к исполняемому - так уже сами делайте.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Не знаю, почему не включили. М.б. лень было комитетчикам думать над этой функцией. Логически я уже намекнул, почему ей не место в std::filesystem. C++ не обязывает код запускаться из ОС, и никаких процессов в системе может не быть. А файловая система не относится к рантайму (и также не продполагает использование ОС) и изменение текущего каталога это просто для удобства использования остальных функций std::filesystem.

Любопытно, что в Rust не побрезговали реализовать эту функцию, только лежит она (ожидаемо) в std::env, а не в std::fs.

seiken ★★★★★
()
Ответ на: комментарий от seiken

Любопытно, что в Rust не побрезговали реализовать эту функцию,

В расте вроде забили на подход «мы не будем поддерживать это, потому что на 0.01% прошлотысячелетних тостеров целые числа могут быть -0.»

anonymous
()
Ответ на: комментарий от seiken

Логически я уже намекнул, почему ей не место в std::filesystem

по твоей логике и current_dir() там не должна находиться.

safocl ★★
() автор топика
Ответ на: комментарий от seiken

А файловая система не относится к рантайму

как понять не относится — енто по твоему чото статическое относительно проги? она не меняется при выполнении проги?

safocl ★★
() автор топика
Ответ на: комментарий от safocl

Загрузчик ОС. Он знает структуру ФС, но его не существует как процесса ОС и как файл в ОС он тоже не обязан существовать. current_dir нужна чтобы можно было работать с относительными путями, т.е. в принципе, она лишняя, и комитетчики просто сделали нам одолжение, включив эту функцию для упрощения работы. ФС не относится к рантайму в том смысле, что std::filesystem работает с ФС, а ФС это традиционно персистентная сущность. В отличие от выполняющейся программы.

seiken ★★★★★
()
Последнее исправление: seiken (всего исправлений: 1)
Ответ на: комментарий от seiken

Да блин, по такой логике вообще нужно в STL ничего не включать больше. Какая разница, что относится к рантайму, а что нет. Это демагогия. Главное - это удобство использования. Если функция такая нужна, то хорошо бы ее иметь в STL.

rumgot ★★★★★
()
Ответ на: комментарий от seiken

включив эту функцию для упрощения работы

а для чего еще улучшается язык?

т.е. в принципе, она лишняя

чо за ересевая ахинеальная бредятина?

ФС это традиционно персистентная сущность

персистентность енто ваще не относится ник рантаймовости, ни к статичности... ты путаешь теплае с мягким... — файловая система как раз таки участвует в рантайме как никто другой, если ее использует прога.

safocl ★★
() автор топика
Ответ на: комментарий от seiken

Он знает структуру ФС, но его не существует как процесса ОС и как файл в ОС он тоже не обязан существовать.

тогда current_dir вернёт пустую строку и все довольны

next_time ★★★★★
()
Ответ на: комментарий от slovazap

Это говённый код который надо допиливать под каждую новую систему чтобы запустить

так задача в принципе OS-специфичная

и скорее всего, как обычно, толком не решённая

вангую, что стандартные библиотеки, не только плюсовые, сломаются уже на банальных iOS и Android

next_time ★★★★★
()
Ответ на: комментарий от safocl

Если портабельная - использовать относительные пути. Если установленная - сконфигурированные при сборке. Просто завести сборочный флаг Portable чтобы выставлять относительные пути автоматом.

next_time ★★★★★
()
Ответ на: комментарий от next_time

Более того, для решения этой задачи даже в дебри CMAKE лезть не придётся, всё напрямую в коде делается:

//file realative.h
#pragma once
#include <string>

namespace Paths
{
inline std::string path1()
{
  return "folder1/file1.txt";
}
inline std::string path2()
{
  return "folder2/file2.txt";
} 
...
}
//file absolute.h
#pragma once
#include <string>

namespace Paths
{
inline std::string path1()
{
  return "/etc/folder1/file1.txt";
}
inline std::string path2()
{
  return "/usr/share/folder2/file2.txt";
} 
...
}
//paths.h
#pragma once

#define RELATIVE_PATHS 1

#if RELATIVE_PATHS
  #include "realative.h"
#else
  #include "absolute.h"
#endif

next_time ★★★★★
()
Последнее исправление: next_time (всего исправлений: 2)
Ответ на: комментарий от next_time

вангую, что стандартные библиотеки, не только плюсовые, сломаются уже на банальных iOS и Android

а чо не так с iOS и Android?

safocl ★★
() автор топика

Три страницы. Отлично.

И это ещё один вариант ответа на вопрос «зачем люди используют QtCore». Там это делается одной строкой.

Не-не, я не агитирую ТСа использовать QtCore. Вполне возможно, что именно ему это не нужно или не подойдёт. Просто иллюстрация того, что QtCore — хорошее дополнение к STL. И ответ тем, кто в 2021 году упёрто считает Qt «графическим тулкитом».

hobbit ★★★★★
()
Ответ на: комментарий от rumgot

Вообще конечно такую элементарщину могли бы и включить в стандарт

+1

hobbit ★★★★★
()
Ответ на: комментарий от next_time

вангую, что стандартные библиотеки, не только плюсовые, сломаются уже на банальных iOS и Android

На iOS возможно, а на андроиде-то почему? Это таки Linux, хоть и не GNU. Хотя да, тут надо просто брать и проверять.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

И это ещё один вариант ответа на вопрос «зачем люди используют QtCore»

Согласен. Но там я чуть выше привел свою реализацию коммент, которая как раз сделана на основе того, как сделано в Qt. Там ведь очень мало. Только лишь ради этого тянуть Qt Core - ну не знаю, ИМХО конечно, но я бы не стал.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от rumgot

«Только лишь ради этого» – не надо, согласен. Так ведь весь QtCore из таких «мелочей» сложен. QLibrary, например.

SDL в этом смысле в чём-то аналогичен QtCore. Там тоже есть своя кроссплатформенная затычка для dlopen/LoadLibrary, своя, как ты выше написал, работа с путями и ещё дофига всего.

hobbit ★★★★★
()
Ответ на: комментарий от safocl

огороженность + волшебные китайские сборки в случае андроида

next_time ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.