Как использовать VS C ++ GetEnvironmentVariable максимально чисто? - PullRequest
4 голосов
/ 09 ноября 2010

(Это не такая большая проблема, как упражнение в педантичности, так что здесь.)

Я сделал хорошую маленькую программу, которая встроена в мою ОС Linux, но я думаю, что этодостаточно полезно, чтобы существовать на моей машине с Windows тоже.Таким образом, я хотел бы получить доступ к переменным среды Windows, и MSDN приводит пример, подобный этому:

const DWORD buff_size = 50;
LPTSTR buff = new TCHAR[buff_size];

const DWORD var_size = GetEnvironmentVariable("HOME",buff,buff_size);

if (var_size==0) { /* fine, some failure or no HOME */ }
else if (var_size>buff_size) {

    // OK, so 50 isn't big enough.
    if (buff) delete [] buff;
    buff = new TCHAR[var_size];

    const DWORD new_size = GetEnvironmentVariable("HOME",buff,var_size);

    if (new_size==0 || new_size>var_size) { /* *Sigh* */ }
    else { /* great, we're done */ }
}
else { /* in one go! */ }

Это не так хорошо (для меня), как использование getenv и просто проверка на нулевой указатель,Я также предпочел бы не распределять память динамически, так как я просто пытаюсь заставить программу работать как в Windows, так и в моей ОС Linux, что означает, что этот код MS должен хорошо играть с кодом nix.Более конкретно:

template <class T> // let the compiler sort out between char* and TCHAR*
inline bool get_home(T& val) { // return true if OK, false otherwise
#if defined (__linux) || (__unix)
    val = getenv("HOME");
    if (val) return true;
    else return false;
#elif defined (WINDOWS) || defined (_WIN32) || defined (WIN32)
    // something like the MS Code above
#else
    // probably I'll just return false here.
#endif
}

Итак, мне нужно было бы выделить в куче универсально или сделать #ifdef в вызывающих функциях, чтобы освободить память.Не очень красиво.

Конечно, я мог бы просто разместить 'buff' в стеке, но тогда мне пришлось бы создать новый TCHAR[], если 'buff_size' был недостаточно большим.на мой первый вызов GetEnvironmentVariable.Лучше, но что, если бы я был педантом и не хотел создавать лишние массивы?Есть какие-нибудь идеи по поводу чего-то более эстетически привлекательного?

Я не настолько осведомлен, так что кто-нибудь может завидовать мне намеренно, заставляя GetEnvironmentVariable потерпеть неудачу, чтобы получить размер строки?Кто-нибудь видит проблему с:

const DWORD buff_size = GetEnvironmentVariable("HOME",0,0);
TCHAR buff[buff_size];
const DWORD ret = GetEnvironmentVariable("HOME",buff,buff_size);
// ...

Любые другие идеи или предложения?(Или исправления к вопиющим ошибкам?)

ОБНОВЛЕНИЕ: много полезной информации ниже.Я думаю, что лучшим вариантом для того, что я пытаюсь сделать, является использование static char[] типа:

inline const char* get_home(void) { // inline not required, but what the hell.
#if defined (__linux) || (__unix)
    return getenv("HOME");
#elif defined (WINDOWS) || defined (WIN32) || defined (_WIN32)
    static char buff[MAX_PATH];
    const DWORD ret = GetEnvironmentVariableA("USERPROFILE",buff,MAX_PATH);
    if (ret==0 || ret>MAX_PATH)
        return 0;
    else
        return buff;
 #else
        return 0;
 #endif
 }

Возможно, это не самый элегантный способ сделать это, но это, вероятно, самый простой способ синхронизациидо того, что я хочу сделать между * nix и Windows.(Я также буду беспокоиться о поддержке Unicode позже.)

Спасибо за помощь, ребята.

Ответы [ 5 ]

9 голосов
/ 09 ноября 2010
DWORD bufferSize = 65535; //Limit according to http://msdn.microsoft.com/en-us/library/ms683188.aspx
std::wstring buff;
buff.resize(bufferSize);
bufferSize = GetEnvironmentVariableW(L"Name", &buff[0], bufferSize);
if (!bufferSize)
    //error
buff.resize(bufferSize);

Конечно, если вы хотите ASCII, замените wstring на string и GetEnvironmentVariableW на GetEnvironmentVariableA.

РЕДАКТИРОВАТЬ: Вы также можете создать getenv самостоятельно. Это работает, потому что

Эта же ячейка памяти может использоваться при последующих вызовах getenv, перезаписывая предыдущий контент.

const char * WinGetEnv(const char * name)
{
    const DWORD buffSize = 65535;
    static char buffer[buffSize];
    if (GetEnvironmentVariableA(name, buffer, buffSize))
    {
        return buffer;
    }
    else
    {
        return 0;
    }
}

Конечно, было бы неплохо использовать широкие символьные версии всего этого, если вы хотите поддерживать поддержку юникода.

4 голосов
/ 09 ноября 2010

VC ++ реализует getenv в stdlib.h, см., Например, здесь .

1 голос
/ 23 февраля 2012

Это не был оригинальный вопрос, но, возможно, стоит добавить путь MFC к этой теме для справки:

CString strComSpec;
if (strComSpec.GetEnvironmentVariable(_T("COMSPEC")))
{
    //Do your stuff here
}
0 голосов
/ 09 ноября 2010

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

Единственное, что вы можете сделать, это передать буфер наилучшего предположения и его размер при первом вызове и вызывать его снова только в случае сбоя.

0 голосов
/ 09 ноября 2010

Не беспокойся.%HOME% - это путь в Windows, который должен использоваться всеми разумными программами.Следовательно, он будет вписываться в WCHAR[MAX_PATH].Вам не нужно иметь дело с крайним случаем, когда он длиннее этого - если он длиннее, большинство файловых функций все равно отклонят его, так что вы также можете рано потерпеть неудачу.

Однако, делать не предполагается, что вы можете использовать TCHAR[MAX_PATH] или char[MAX_PATH].Вы не можете контролировать содержимое %HOME%;он будет содержать имя пользователя.Если это «Андре» (т.е. не ASCII), вы должны хранить %HOME% в WCHAR[MAX_PATH].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...