在不使用/proc/self/exe的情况下找到当前可执行文件的路径

234

我认为Linux的/proc/self/exe很容易使用。但我想知道是否有跨平台接口的方便方法来查找当前应用程序的目录。我见过一些项目在argv[0]上操作,但它似乎不是完全可靠的。

如果您曾经需要支持例如Mac OS X这样没有/proc/的系统,您会怎么做?使用#ifdef来隔离特定于平台的代码(例如NSBundle)吗?还是尝试从argv [0],$PATH等中推断出可执行文件的路径,冒着在边缘情况下发现错误的风险?


重复:https://dev59.com/KHNA5IYBdhLWcg3wgeId - Greg Hewgill
我谷歌搜索了一下:获取我的 ps -o comm。 让我来到这里的是:"/proc/pid/path/a.out"。 - basin
1
在我看来,prideout的回答应该排在首位,因为它正确地解决了“跨平台接口”的要求,并且非常容易集成。 - Stéphane Gourichon
1
@ALX23z std::filesystem::current_path() 不会返回当前可执行文件的路径,而是返回当前工作目录的路径。 - Ted Lyngmo
1
@ALX23z 不,当前目录永远不会与可执行文件相同。 - Ted Lyngmo
显示剩余2条评论
15个回答

1
当然,并不是所有项目都适用这个方法。但是,在6年的C++/Qt开发中,QCoreApplication::applicationFilePath()从未让我失望。
当然,在尝试使用它之前,应该仔细阅读文档:
警告:在Linux上,此函数将尝试从/proc文件系统获取路径。如果失败,则假定argv[0]包含可执行文件的绝对文件名。该函数还假定当前目录未被应用程序更改。
老实说,我认为像#ifdef和其他解决方案在现代代码中根本不应该使用。
我相信也存在更小的跨平台库。让它们将所有特定于平台的东西封装起来。

我正在使用GTK,是否没有可用的跨平台GTK解决方案? - Melroy van den Berg

0

8
这并不是可靠的(虽然它通常可以使用常规 shell 启动的程序),因为 execv 等函数会将可执行文件路径与 argv 分开处理。 - dmckee --- ex-moderator kitten
10
这是一个不正确的答案。它可能会告诉你在哪里找到一个同名的程序,但它并没有告诉你当前正在运行的可执行文件实际的位置在哪里。 - Larry Gritz

0

你不能这样做(至少在Linux上)

由于可执行文件可以在运行它的进程执行期间将其文件路径重命名为同一文件系统中的不同目录。另请参见syscalls(2)inode(7)

但我想知道是否有一种方便的方法来使用跨平台接口在C/C++中查找当前应用程序的目录。

在Linux上,一个可执行文件甚至可以(原则上)通过调用unlink(2)remove(3) 自己。然后Linux内核应该保留文件分配,直到没有进程再引用它为止。使用proc(5)你可以做很奇怪的事情(例如rename(2)那个 /proc/self/exe文件等...)

换句话说,在Linux上,“当前应用程序目录”的概念没有任何意义。

还可以阅读Advanced Linux ProgrammingOperating Systems: Three Easy Pieces获取更多信息。

还可以在OSDEV上查看多个开源操作系统(包括FreeBSDGNU Hurd)。其中一些提供接口(API)与 POSIX 接口相近。

考虑使用经过许可的跨平台 C++ 框架,例如QtPOCO,或者通过将它们移植到您喜欢的操作系统上来为它们做出贡献。


所以,如果您有相对于可执行文件的资源文件,您会在代码中硬编码到资源的路径中吗?:) 即使存在某些(相当模糊的)用例,其中一个进程运行了来自任何路径上的任何文件都没有,通常情况下您希望获得可执行文件的绝对路径,以便查找其它位于该路径下的内容。 - BitTickler

0

仅供参考。您可以使用此代码在C/C++中通过跨平台接口找到当前应用程序的目录。

void getExecutablePath(char ** path, unsigned int * pathLength)
{
    // Early exit when invalid out-parameters are passed
    if (!checkStringOutParameter(path, pathLength))
    {
        return;
    }

#if defined SYSTEM_LINUX

    // Preallocate PATH_MAX (e.g., 4096) characters and hope the executable path isn't longer (including null byte)
    char exePath[PATH_MAX];

    // Return written bytes, indicating if memory was sufficient
    int len = readlink("/proc/self/exe", exePath, PATH_MAX);

    if (len <= 0 || len == PATH_MAX) // memory not sufficient or general error occured
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    copyToStringOutParameter(exePath, len, path, pathLength);

#elif defined SYSTEM_WINDOWS

    // Preallocate MAX_PATH (e.g., 4095) characters and hope the executable path isn't longer (including null byte)
    char exePath[MAX_PATH];

    // Return written bytes, indicating if memory was sufficient
    unsigned int len = GetModuleFileNameA(GetModuleHandleA(0x0), exePath, MAX_PATH);
    if (len == 0) // memory not sufficient or general error occured
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    copyToStringOutParameter(exePath, len, path, pathLength);

#elif defined SYSTEM_SOLARIS

    // Preallocate PATH_MAX (e.g., 4096) characters and hope the executable path isn't longer (including null byte)
    char exePath[PATH_MAX];

    // Convert executable path to canonical path, return null pointer on error
    if (realpath(getexecname(), exePath) == 0x0)
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    unsigned int len = strlen(exePath);
    copyToStringOutParameter(exePath, len, path, pathLength);

#elif defined SYSTEM_DARWIN

    // Preallocate PATH_MAX (e.g., 4096) characters and hope the executable path isn't longer (including null byte)
    char exePath[PATH_MAX];

    unsigned int len = (unsigned int)PATH_MAX;

    // Obtain executable path to canonical path, return zero on success
    if (_NSGetExecutablePath(exePath, &len) == 0)
    {
        // Convert executable path to canonical path, return null pointer on error
        char * realPath = realpath(exePath, 0x0);

        if (realPath == 0x0)
        {
            invalidateStringOutParameter(path, pathLength);
            return;
        }

        // Copy contents to caller, create caller ownership
        unsigned int len = strlen(realPath);
        copyToStringOutParameter(realPath, len, path, pathLength);

        free(realPath);
    }
    else // len is initialized with the required number of bytes (including zero byte)
    {
        char * intermediatePath = (char *)malloc(sizeof(char) * len);

        // Convert executable path to canonical path, return null pointer on error
        if (_NSGetExecutablePath(intermediatePath, &len) != 0)
        {
            free(intermediatePath);
            invalidateStringOutParameter(path, pathLength);
            return;
        }

        char * realPath = realpath(intermediatePath, 0x0);

        free(intermediatePath);

        // Check if conversion to canonical path succeeded
        if (realPath == 0x0)
        {
            invalidateStringOutParameter(path, pathLength);
            return;
        }

        // Copy contents to caller, create caller ownership
        unsigned int len = strlen(realPath);
        copyToStringOutParameter(realPath, len, path, pathLength);

        free(realPath);
    }

#elif defined SYSTEM_FREEBSD

    // Preallocate characters and hope the executable path isn't longer (including null byte)
    char exePath[2048];

    unsigned int len = 2048;

    int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };

    // Obtain executable path by syscall
    if (sysctl(mib, 4, exePath, &len, 0x0, 0) != 0)
    {
        invalidateStringOutParameter(path, pathLength);
        return;
    }

    // Copy contents to caller, create caller ownership
    copyToStringOutParameter(exePath, len, path, pathLength);

#else

    // If no OS could be detected ... degrade gracefully
    invalidateStringOutParameter(path, pathLength);

#endif
}

您可以在此处详细查看


关于您代码中的FREEBSD部分:我认为getprogname()是更符合惯用方式的方法(需要包含<stdlib.h>,因为它是libc的一部分)。 - BitTickler

-1

我在标准中没有找到它的工作方式,但在C++17及以后的版本中,我测试过的所有平台上都可以使用:

std::filesystem::canonical(argv[0])

尽管argv [0]根据平台包含不同的内容,但在规范化后,它仍然有效。它提供了可执行文件的绝对路径,无论当前工作目录如何。


只有当调用不是通过路径(例如在/usr/local/bin中的二进制文件)时才会执行。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接