Как я могу рассчитать полный размер буфера для GetModuleFileName? - PullRequest
12 голосов
/ 30 апреля 2009

GetModuleFileName() принимает буфер и размер буфера в качестве входных данных; однако его возвращаемое значение может только сказать нам, сколько символов скопировано, и если размер недостаточен (ERROR_INSUFFICIENT_BUFFER).

Как определить реальный необходимый размер буфера для хранения полного имени файла для GetModuleFileName()?

Большинство людей используют MAX_PATH, но я помню, что путь может превышать это (260 по умолчанию) ...

(хитрость использования нуля в качестве размера буфера не работает для этого API - я уже пробовал ранее)

Ответы [ 7 ]

10 голосов
/ 30 апреля 2009

Обычный рецепт - вызвать его, установив размер на ноль, и он гарантированно потерпит неудачу и предоставит размер, необходимый для выделения достаточного буфера. Выделите буфер (не забудьте место для нулевого завершения) и вызовите его во второй раз.

Во многих случаях достаточно MAX_PATH, поскольку многие файловые системы ограничивают общую длину имени пути. Тем не менее, можно создать допустимые и полезные имена файлов, которые превышают MAX_PATH, поэтому, вероятно, это хороший совет для запроса требуемого буфера.

Не забудьте в конце концов вернуть буфер из распределителя, который его предоставил.

Редактировать: Фрэнсис в комментарии отмечает, что обычный рецепт не работает для GetModuleFileName(). К сожалению, Фрэнсис абсолютно прав в этом вопросе, и мое единственное оправдание заключается в том, что я не пошел, чтобы проверить его, прежде чем предлагать «обычное» решение.

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

О, да, не забывайте, что имена путей с 1995 года или около того допускают символы Юникода. Поскольку Unicode занимает больше места, любому имени пути может предшествовать \\?\, чтобы явно запросить, чтобы ограничение MAX_PATH на его длину в байтах было снято для этого имени. Это усложняет вопрос.

MSDN имеет это, чтобы сказать о длине пути в статье под названием Имена файлов, пути и пространства имен :

Максимальная длина пути

В Windows API (с некоторыми исключения обсуждаются в следующем абзацы), максимальная длина для путь MAX_PATH, который определяется как 260 символов. Локальный путь структурирован в следующем порядке: буква диска, двоеточие, обратный слеш, компоненты, разделенные обратной косой чертой, и завершающий нулевой символ. За Например, максимальный путь на диске D это "D:\<some 256 character path string><NUL>", где "<NUL>" представляет невидимый завершающий ноль символ для текущей системы кодовая. (Используются символы < > здесь для наглядности и не может быть часть допустимой строки пути.)

Примечание. Функции ввода-вывода файла в Windows API преобразует "/" в "\" как часть преобразования имени в стиль NT имя, кроме случаев использования "\\?\" префикс, как описано в следующем разделы.

Windows API имеет много функций которые также имеют версии Unicode для разрешить путь расширенной длины для максимальная общая длина пути 32 767 персонажи. Этот тип пути состоит из компонентов, разделенных обратная косая черта, каждая до значения вернулся в lpMaximumComponentLength параметр функция GetVolumeInformation. к укажите путь расширенной длины, используйте префикс "\\?\". Например, "\\?\D:\<very long path>". (The символы < > используются здесь для визуальная четкость и не может быть частью допустимая строка пути.)

Примечание. Максимальный путь 32 767 символы приблизительны, потому что Префикс "\\?\" может быть расширен до длинная строка системой при запуске время, и это расширение относится к Общая длина.

Также можно использовать префикс "\\?\" с путями, построенными в соответствии с универсальное соглашение об именах (UNC). Чтобы указать такой путь с помощью UNC, используйте префикс "\\?\UNC\". Например, "\\?\UNC\server\share", где "сервер" это имя машины и "поделиться" это имя общей папки. Эти префиксы не используются как часть сам путь. Они указывают, что путь должен быть передан система с минимальной модификацией, а это значит, что вы не можете использовать косая черта для представления пути разделители, или период для представления текущий каталог. Вас также не может использовать префикс "\\?\" с относительный путь, следовательно, относительный пути ограничены MAX_PATHсимволы, как указано ранее для пути, не использующие префикс "\\?\".

При использовании API для создания каталог, указанный путь не может быть настолько длинным, что вы не можете добавить 8.3 имя файла (то есть имя каталога не может превышать MAX_PATH минус 12).

Оболочка и файловая система имеют разные требования. Это возможно создать путь с помощью Windows API что пользовательский интерфейс оболочки может не сможет справиться.

Так что простым ответом было бы выделить буфер размером MAX_PATH, извлечь имя и проверить на наличие ошибок. Если это подходит, вы сделали. В противном случае, если он начинается с "\\?\", получите буфер размером 64 КБ или около того (приведенная выше фраза "максимальный путь из 32 767 символов является приблизительным" вызывает некоторые сложности, поэтому я оставляю некоторые детали для дальнейшего изучения) попробуйте еще раз.

Переполнение MAX_PATH, но не начинающееся с "\\?\", похоже на случай "не может быть". Опять же, что делать, это деталь, с которой вам придется иметь дело.

Также может возникнуть некоторая путаница в отношении ограничения длины пути для сетевого имени, начинающегося с "\\Server\Share\", не говоря уже об именах из пространства имен объектов ядра, которые начинаются с "\\.\". В приведенной выше статье ничего не сказано, и я не уверен, может ли этот API возвращать такой путь.

9 голосов
/ 30 апреля 2009

Реализуйте разумную стратегию для увеличения буфера, например, начните с MAX_PATH, затем увеличьте каждый последующий размер в 1,5 раза (или в 2 раза за меньшее количество итераций), чем предыдущий. Повторяйте, пока функция не выполнится успешно.

2 голосов
/ 21 января 2015

Хотя API является доказательством плохого дизайна, решение на самом деле очень простое. Простой, но грустный, так должно быть, потому что это в некоторой степени снижает производительность, поскольку может потребовать многократного выделения памяти. Вот некоторые ключевые моменты к решению:

  • Вы не можете полагаться на возвращаемое значение между различными версиями Windows, так как оно может иметь разную семантику в разных версиях Windows (например, XP).

  • Если предоставленный буфер слишком мал для размещения строки, возвращаемое значение - это количество символов, включая терминатор 0.

  • Если предоставленный буфер достаточно велик, чтобы содержать строку, возвращаемое значение - это количество символов, исключая 0-терминатор.

Это означает, что если возвращаемое значение точно равно размеру буфера, вы все равно не знаете, успешно оно или нет. Там может быть больше данных. Или нет. В конце концов, вы можете быть уверены в успехе, только если длина буфера на самом деле больше, чем требуется. К сожалению ...

Итак, решение - начать с небольшого буфера. Затем мы вызываем GetModuleFileName, передавая точную длину буфера (в TCHAR) и сравнивая с ней возвращаемый результат. Если возвращаемый результат меньше длины нашего буфера, он успешен. Если возвращаемый результат больше или равен длине нашего буфера, мы должны повторить попытку с большим буфером. Промыть и повторить до готовности. Когда мы закончим, мы сделаем копию строки (strdup / wcsdup / tcsdup) из буфера, очистим и вернем копию строки. Эта строка будет иметь правильный размер размещения, а не вероятные издержки из нашего временного буфера. Обратите внимание, что вызывающая сторона отвечает за освобождение возвращенной строки (память mallocs strdup / wcsdup / tcsdup).

См. Пример кода реализации и использования ниже. Я использую этот код уже более десяти лет, в том числе в программном обеспечении для управления документооборотом предприятия, где может быть много длинных путей. Конечно, код можно оптимизировать различными способами, например, сначала загрузив возвращенную строку в локальный буфер (TCHAR buf [256]). Если этот буфер слишком мал, вы можете запустить цикл динамического распределения. Возможна и другая оптимизация, но это выходит за рамки.

Пример реализации и использования:

/* Ensure Win32 API Unicode setting is in sync with CRT Unicode setting */
#if defined(_UNICODE) && !defined(UNICODE)
#   define UNICODE
#elif defined(UNICODE) && !defined(_UNICODE)
#   define _UNICODE
#endif

#include <stdio.h> /* not needed for our function, just for printf */
#include <tchar.h>
#include <windows.h>

LPCTSTR GetMainModulePath(void)
{
    TCHAR* buf    = NULL;
    DWORD  bufLen = 256;
    DWORD  retLen;

    while (32768 >= bufLen)
    {
        if (!(buf = (TCHAR*)malloc(sizeof(TCHAR) * (size_t)bufLen))
        {
            /* Insufficient memory */
            return NULL;
        }

        if (!(retLen = GetModuleFileName(NULL, buf, bufLen)))
        {
            /* GetModuleFileName failed */
            free(buf);
            return NULL;
        }
        else if (bufLen > retLen)
        {
            /* Success */
            LPCTSTR result = _tcsdup(buf); /* Caller should free returned pointer */
            free(buf);
            return result;
        }

        free(buf);
        bufLen <<= 1;
    }

    /* Path too long */
    return NULL;
}

int main(int argc, char* argv[])
{
    LPCTSTR path;

    if (!(path = GetMainModulePath()))
    {
        /* Insufficient memory or path too long */
        return 0;
    }

    _tprintf("%s\n", path);

    free(path); /* GetMainModulePath malloced memory using _tcsdup */ 

    return 0;
}

Сказав все это, я хотел бы отметить, что вам необходимо быть в курсе различных других предостережений с GetModuleFileName (Ex). Существуют различные проблемы между 32/64-битным / WOW64. Кроме того, вывод не обязательно является полным, длинным путем, но вполне может быть коротким именем файла или иметь псевдоним пути. Я ожидаю, что когда вы используете такую ​​функцию, цель состоит в том, чтобы предоставить вызывающей стороне полезный, надежный полный, длинный путь, поэтому я предлагаю действительно обеспечить возвращение полезного, надежного, полного, длинного абсолютного пути таким образом, он переносим между различными версиями Windows и архитектурами (опять же 32/64-bit / WOW64). Как сделать это эффективно выходит за рамки здесь.

Хотя это один из худших из существующих Win32 API, я все же желаю вам радости от программирования.

1 голос
/ 27 июля 2013

Использование

extern char* _pgmptr

может работать.

Из документации GetModuleFileName:

Глобальная переменная _pgmptr автоматически инициализируется по полному пути исполняемого файла и может использоваться для получения полного пути к исполняемому файлу.

Но если я прочту о _pgmptr:

Когда программа не запускается из командной строки, _pgmptr может инициализироваться именем программы (базовое имя файла без расширения имени файла) или именем файла, относительным путем или полным путем.

Кто-нибудь, кто знает, как инициализируется _pgmptr? Если бы SO поддерживал последующие вопросы, я бы опубликовал этот вопрос в качестве продолжения.

0 голосов
/ 21 октября 2017

Мой пример - конкретная реализация подхода «если сначала у вас ничего не получится, удвойте длину буфера». Он получает путь к исполняемому исполняемому файлу, используя строку (на самом деле wstring, так как я хочу иметь возможность обрабатывать Unicode) в качестве буфера. Чтобы определить, когда он успешно получил полный путь, он проверяет значение, возвращаемое из GetModuleFileNameW, против значения, возвращенного wstring::length(), а затем использует это значение для изменения размера окончательной строки, чтобы убрать лишние нулевые символы. Если это не удается, он возвращает пустую строку.

inline std::wstring getPathToExecutableW() 
{
    static const size_t INITIAL_BUFFER_SIZE = MAX_PATH;
    static const size_t MAX_ITERATIONS = 7;
    std::wstring ret;
    DWORD bufferSize = INITIAL_BUFFER_SIZE;
    for (size_t iterations = 0; iterations < MAX_ITERATIONS; ++iterations)
    {
        ret.resize(bufferSize);
        DWORD charsReturned = GetModuleFileNameW(NULL, &ret[0], bufferSize);
        if (charsReturned < ret.length())
        {
            ret.resize(charsReturned);
            return ret;
        }
        else
        {
            bufferSize *= 2;
        }
    }
    return L"";
}
0 голосов
/ 08 апреля 2015

Windows не может правильно обрабатывать пути длиннее 260 символов, поэтому просто используйте MAX_PATH. Вы не можете запустить программу, путь которой длиннее MAX_PATH.

0 голосов
/ 13 января 2015

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

// assume argv is there and a char** array

int        nAllocCharCount = 1024;
int        nBufSize = argv[0][0] ? strlen((char *) argv[0]) : nAllocCharCount;
TCHAR *    pszCompleteFilePath = new TCHAR[nBufSize+1];

nBufSize = GetModuleFileName(NULL, (TCHAR*)pszCompleteFilePath, nBufSize);
if (!argv[0][0])
{
    // resize memory until enough is available
    while (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
    {
        delete[] pszCompleteFilePath;
        nBufSize += nAllocCharCount;
        pszCompleteFilePath = new TCHAR[nBufSize+1];
        nBufSize = GetModuleFileName(NULL, (TCHAR*)pszCompleteFilePath, nBufSize);
    }

    TCHAR * pTmp = pszCompleteFilePath;
    pszCompleteFilePath = new TCHAR[nBufSize+1];
    memcpy_s((void*)pszCompleteFilePath, nBufSize*sizeof(TCHAR), pTmp, nBufSize*sizeof(TCHAR));

    delete[] pTmp;
    pTmp = NULL;
}
pszCompleteFilePath[nBufSize] = '\0';

// do work here
// variable 'pszCompleteFilePath' contains always the complete path now

// cleanup
delete[] pszCompleteFilePath;
pszCompleteFilePath = NULL;

У меня не было случая, чтобы argv не содержал пути к файлу (Win32 и Win32-консольное приложение), пока. Но на всякий случай есть запасной вариант решения, которое было описано выше. Мне немного некрасиво, но работа по-прежнему выполняется.

...