【问题标题】:How to use VS C++ GetEnvironmentVariable as cleanly as possible?如何尽可能干净地使用 VS C++ GetEnvironmentVariable?
【发布时间】:2010-11-09 03:11:43
【问题描述】:

(这与其说是学究气的练习,倒不如说是个问题。)

我已经制作了一个不错的小程序,它是我的 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”,但是如果在第一次调用 GetEnvironmentVariable 时“buff_size”不够大,我就必须创建一个新的TCHAR[]。更好,但是如果我是一个书呆子并且不想到处创建多余的数组怎么办?有什么更美观的想法吗?

我不是很了解,所以有人会嫉妒我故意强迫 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 支持。)

感谢所有帮助过的人。

【问题讨论】:

  • 在 C++ 中,您应该将值作为std::basic_string&lt;TCHAR&gt; 返回。这解决了内存管​​理问题。但是,请注意%HOME% 默认没有设置。您可能正在寻找%USERPROFILE%、`%APPDATA%` 或%LOCALAPPDATA%
  • 我对windows环境变量不熟悉,所以默认我知道的!是的,%USERPROFILE% 是我需要的。返回字符串值听起来是个好主意!我可以只返回一个空字符串作为失败指示器。当我懒得放下笔记本电脑时,我会试一试……
  • 十多年后,我也感受到了同样的痛苦:(冒着被贴上书呆子标签的巨大风险(我会为此获得一个链接徽章吗?),我认为你的支票应该被收回 > = MAX_PATH 正如 API 文档所说:“如果 lpBuffer 不足以容纳数据,则返回值是缓冲区大小,以字符为单位,需要容纳字符串及其终止空字符和 lpBuffer 的内容是未定义的。” ,因此 MAX_PATH 长度将是一个错误。我认为。对不起!我认为在调用之间也存在值变化的理论风险,这可能意味着您的第二次调用可能会失败。
  • @MarkH 啊,是的,谢谢。是的,应该考虑潜在的竞争条件。敢说自从我第一次问这个问题以来事情已经发生了很大的变化,也许 Windows API 现在对我的问题有一个非常不同的规范答案?我不能说,但谢谢你的两点(和同情!)。

标签: c++ visual-c++


【解决方案1】:
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;
    }
}

当然,如果您想保持对 unicode 的支持,最好使用所有这些的宽字符版本。

【讨论】:

  • 调整大小。是的,那会更容易......(该死的)。编辑:虽然,它仍然在搞乱内存分配(尊严保存了一点点?)。有没有办法通过只分配一次内存来做到这一点,还是这是一个愚蠢的问题?
  • @Zorawar:在大多数 STL 实现中,如果您将resize 分配到较低值的内存,则不会重新分配内存。因此这里只有一个分配。
  • 我想您可以使用堆栈分配的缓冲区来执行此操作,但 131,070 字节对于放入堆栈来说是一个非常大的值。
  • 是的,这就是为什么我不想再猜测环境变量的大小并无缘无故地用完所有内存!没有任何关于我强迫GetEnvironmentVariable 故意失败的想法的回复,以便我可以获得可变大小。我想这要么不是一个好主意,要么是多余的......
  • @Zorawar:我相信,故意的失败是访问此 API 的“正常”类 C 方式。这样做确实是一种权衡。您花费了更多内存,但您节省了两次调用该函数。 GetEnvironmentVariable 本身就有合理的开销。您可能可以在 GetEnvironmentStrings 之上设计一个更快的版本(因为它仅返回指向进程环境块的指针),但我认为这样的代码不是性能关键代码。
【解决方案2】:

这不是最初的问题,但可能值得将 MFC 方式添加到此线程以供参考:

CString strComSpec;
if (strComSpec.GetEnvironmentVariable(_T("COMSPEC")))
{
    //Do your stuff here
}

【讨论】:

    【解决方案3】:

    VC++ 在 stdlib.h 中实现 getenv,例如,参见 here

    【讨论】:

    • 我现在不在我的 Windows 机器上,但是当我尝试使用 getenv 编译时,它吐出了一些关于弃用的信息(除非我被阻碍并误读了错误)。即使 getenv 在标准库中,我也没有给予太多关注,并且我有一半认为 MS 对此很迟钝。为什么 MS 首先要有 GetEnvironmentVariable?
    • 我不知道。我们在这里使用 getenv 进行 Windows 构建。我认为我们没有任何特殊的编译器标志或任何东西。
    • @Zorawar: GetEnvironmentVariable 是 Win32 API 的一部分,可以从 C 以外的其他语言中使用。getenv 是 C 的一部分,显然会调用 GetEnvironmentVariable。同样,Win32 API 不包含operator newmalloc,但包含GlobalAlloc。当您调用putenv 时可能会出现“已弃用”消息,这不是标准 C 函数。
    • @MSalters:好的,这是有道理的。作为参考,我得到的警告是C4996: 'getenv': This function or variable may be unsafe. Consider using _dupenv_s instead...
    • 理论上,getenv 是不安全的,因为它返回一个指向稍后可以修改的内部结构的指针,例如,通过 putenv。由于您正在开发一个供个人使用的“不错的小程序”,我想您对让事情快速进行而不是理论上的安全问题更感兴趣,所以我认为使用 getenv 是可以的。有关警告的更多信息,请参阅msdn.microsoft.com/en-us/library/8ef0s5kh%28v=VS.80%29.aspx。看起来如果你 #define _CRT_SECURE_NO_WARNINGS 警告就会消失。
    【解决方案4】:

    您在帖子末尾提出的建议是正确的方法 - 调用一次以获取所需的缓冲区大小,然后再次调用以实际获取数据。许多 Win32 API 以这种方式工作,起初令人困惑,但很常见。

    您可以做的一件事是在第一次调用时传入一个最佳猜测缓冲区及其大小,并且只有在失败时才再次调用。

    【讨论】:

    • 不过,我真的可以证明两次调用GetEnvironmentVariable 只是为了在分配内存方面更加迂腐吗?教科书 (C++) 的做法是什么?
    • 这是 Win32,它是一个 C API,而不是 C++。微软在这里制定规则,这是“正确”的做法。如果你只想要一个电话,你必须使用一个足够大的缓冲区来处理你想要处理的任何事情。无论如何,您仍然需要对 API 调用进行错误检查。最大值为 32767,包括终止的 null,因此使用大于该值的缓冲区没有意义。
    • 请记住,两个调用之间存在竞争条件。在一个简单的应用程序中,这不是问题,但严格正确的实现不是特别是两次调用,而是一个循环调用它,直到遇到成功或错误。该循环的预期执行会产生两次调用,但如果有人在两次调用之间更改变量,则循环将使用相应更新的缓冲区进行第三次调用。
    • 缓冲区及其大小都是基于堆栈的。只有非常不寻常的应用程序才会允许在两次调用之间更改任一值,即使存在多个线程。
    【解决方案5】:

    不要打扰。 %HOME% 是 Windows 上的路径,应该可以被所有合理的程序使用。因此,它将适合WCHAR[MAX_PATH]。你不需要处理比这更长的边缘情况 - 如果它更长,大多数文件函数无论如何都会拒绝它,所以你最好早点失败。

    但是,不要假设您可以使用TCHAR[MAX_PATH]char[MAX_PATH]。您无法控制%HOME% 的内容;它将包含用户名。如果那是“André”(即不是 ASCII),则必须将 %HOME% 存储在 WCHAR[MAX_PATH] 中。

    【讨论】:

    • 实际上,在 NT 上,它不需要适合 MAX_PATH,但如果不适合,很多应用程序可能会中断。 ;)(NT 的限制是 32k 个字符)当然,有人可以用他们想要的任何值覆盖该值,即使覆盖不是有效路径。
    • 哦,别告诉我“别打扰”:我这个几乎完全不用 WinAPI!我已经很多年没有正确使用VC++了,但是TCHAR 没有解析为charwchar_t? (还有WCHAR!?)
    • @Zorawar:WCHAR 不是 C 或 C++ 数据类型。它是一个 win32 数据类型。 Win32 不是 C api,它是与语言无关的 API。因此它们不能依赖于特定的 c 类型,如 char 和 wchar_t。在大多数平台上,CHAR 扩展为 char,而 WCHAR 扩展为 wchar_t。如果您使用 Unicode 支持进行编译,TCHAR 将扩展为 WCHAR,如果您在不支持 Unicode 的情况下进行编译,TCHAR 将扩展为 CHAR。
    • @Billy:对,我明白了。感谢您的澄清。
    • @Billy ONeal:32KB 限制仅适用于具有 \\?` prefix, which you're unlikely to see in a %HOME%` 变量的路径 - 它打破了其他常见假设。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-25
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 2016-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多