开发者

为什么获取环境变量getenv小心有坑

开发者 https://www.devze.com 2025-04-01 12:17 出处:网络 作者: 半路杀出来的小黑同学
目录一、背景问题现象二、实验三、解释实验表达了什么?G++下的getenv为什么获得不了环境变量?_putenv()小插曲四、启发总结一、背景
目录
  • 一、背景
    • 问题现象
  • 二、实验
    • 三、解释
      • 实验表达了什么?
      • G++下的getenv为什么获得不了环境变量?
      • _putenv()小插曲
    • 四、启发
      • 总结

        一、背景

        在工作中,所做的项目需要涉及两个不同语言( P/Invoke)的信息传递。最后选定了一种环境变量的传递方式,但这也遇到了getenv带来的大坑!

        问题现象

        我们在C#的exe主流程中通过DllImport,对环境变量进行了设置。随后我通过DllImport来引入C++的函数定义到托管函数内存中,然后我再使用此环境变量时,发现在C++中根本不存在。

        而当查看官方文档对于Environment.SetEnvironmentVariable()的(定义)[https://learn.microsoft.com/zh-cn/dotnet/api/system.environment.getenvironmentvariables?view=net-5.0&viewFallbackFrom=netstandard-1.0]时,可以发现,其功能为:创建、修改或删除存储在当前进程中的环境变量。而通过DllImport加载的C++代码也明明是同一进程呀,为何会出现此种原因???

        二、实验

        在C#中,设置环境变量基本就Environment.SetEnvironmentVariable()一种方法,而在CPP中有三种方法:

        • 标准库方法:getenv 函数
        • Windows.h库方法:_wgetenv 函数以及GetEnvironmentVariable 函数

        首先,先说结果:

        【dotnet构建的EXE + MingW构建的DLL】

        • Environment.SetEnvironmentVariable()函数+ getenv 函数

        为什么获取环境变量getenv小心有坑

        • Environment.SetEnvironmentVariable()函数+ _wgetenv 函数

        为什么获取环境变量getenv小心有坑

        • Environment.SetEnvironmentVariable()函数+ GetEnvironmentVariable 函数

        为什么获取环境变量getenv小心有坑

        【dotnet构建的EXE +MSVC构建的DLL】

        • Environment.SetEnvironmentVariable()函数+ getenv 函数

        为什么获取环境变量getenv小心有坑

        下面是我们的测试代码:

        • C++测试的源代码:
        // getenv函数所需头文件
        #include <cstdlib>
        #include <IOStream>
        // _wgetenv函数所需头文件
        #include <cwchar>
        #include <string>
        // GetEnvironmentVariable函数所需头文件
        #include <windows.h>
        
        extern "C" {
            __declspec(dllexport) const char* get() {
                const char* path_env = std::getenv("PATH_TEST");
                return path_env;
            }
        }
        
        extern "C" {
            __declspeandroidc(dllexport) const char* get_wide() {
                wchar_t* wpath_env = _wgetenv(L"PATH_TEST");
                if (wpath_env != nullptr) {
                    static std::string path_env;
                    path_env.assign(wpath_env, wpath_env + wcslen(wpath_env));
                    return path_env.c_str();
                }
                return nullptr;
            }
        }
        
        extern "C" {
            __declspec(dllexport) const char* get_winapi() {
                static char buffer[4096];
                dwORD result = GetEnvironmentVariable("PATH_TEST", buffer, sizeof(buffer));
                if (result > 0 && result < sizeof(buffer)) {
                    return buffer;
                }
                return nullptr;
            }
        }
        
        • C#测试的源代码:
        using System;
        using System.Runtime.InteropServices;
        
        class Program
        {
            static void Main(string[] args)
            {
                string pathVariable = Environment.GetEnvironmentVariable("PATH_TEST");
                pathVariable += @"C:\Your\New\Path**********";
                Environment.SetEnvironmentVariable("PATH_TEST", pathVariable);
                Console.WriteLine("C# PATH environment variable:");
                Console.WriteLine(Environment.GetEnvironmentVariable("PATH_TEST"));
                Console.WriteLine(python"C++ PATH environment variable:");
                Print();
            }
            [DllImport("TestC.dll", CallingConvention = CallingConvention.Cdecl)]
            public static extern IntPtr get();
            static void Print()
            {
                var s = get();
                string result = Marshal.PtrToStringAnsi(s);
                Console.WriteLine(result);
            }
        
        }
        
        • C++代码构建的编译代码:
        @echo off
        
        if exist build (
            echo Build folder already exists. Deleting...
            rmdir /s /q build
        )
        
        echo Build folder create..
        mkdir build
        
        echo Running CMake configuration...
        cmake -B build -DCMAKE_CXX_COMPILER=g++ -G Ninja
        
        echo Building the project...
        cmake --build build
        
        echo Build completed.
        

        三、解释

        实验表达了什么?

        通过实验可以发现,凡是Windows.h库定义的【获取环境变量】的函数方法,都可以正常获得。只有标准库下面的getenv是获得不了的。

        但需要注意的是,msvc定义的标准库getenv是可以获得的!

        因此,可以明确【g++下的标准库实现是可能存在问题的】。

        G++下的getenv为什么获得不了环境变量?

        我先去看了一下G++此处的源代码:

        /* glibc/stdlib/getenv.c下的代码 */
        
        #include <stdlib.h>
        #include <string.h>
        #include <unistd.h>
        
        char *
        getenv (const char *name)
        {
          if (__environ == NULL || name[0] == '\0')
            return NULL;
        
          size_t len = strlen (name);
          for (char **ep = __environ; *ep != NULL; ++ep)
            {
              if (name[0] == (*ep)[0]
        	  && strncmp (name, *ep, len) == 0 && (*ep)[len] == '=javascript')
        	return *ep + len + 1;
            }
        
          return NULL;
        }
        libc_hidden_def (getenv)
        

        代码可以看出,该环境变量的获取本质就是循环__environ这个字符指针数组来寻找对应名称的环境变量。

        在继续搜索这个变量,发现其是在DLL 首次加载时,CRT(注意是G++的编译,而不是msvc) 会把「操作系统提供的环境变量,而不是进程http://www.devze.com环境」复制到自己的内存空间(CRT的角度是之后这部分环境数据就与系统环境“断开”了),从而完成该数组__environ的初始化,随后的getenv就从该数组里拿。

        由于SetEnvironmentVariable修改的是进程环境的环境变量,因此其两者根本就是在对两个副本环境变量(因为毕竟是进程级,不能影响系统,因此是副本)在操作,所以不互通!

        _putenv()小插曲

        在搜索问题的过程中,发现有人说_putenv()设定的谁都可以获得Environment.GetEnvironmentVariable()以及getenv()。实验了一下,竟然发现真的可以!

        但仔细看了引入其函数的头文件,果不其然是windows.h!

        于是,为什么 C++ 标准库中只有 getenv() 而没有 setenv()?

        • 主要是因为各个操作系统对环境变量的实现和管理存在差异,因此,C++ 标准委员会在设计时,避免引入一个难以在所有系统上实现一致行为的功能。
        • 在 POSIX 系统(如 linux 和 MACOS)上,通常可以使用 setenv() 或 putenv() 来设置环境变量
        • 但在 Windows 上,管理环境变量的方式有所不同(如使用 SetEnvironmentVariable())。

        四、启发

        如何在P/Invoke中使用【获取和修改环境变量】

        • 在Win环境下,「获取环境变量」还是避免使用getenv,统一使用windows.h下的库函数如_putenv()、GetEnvironmentVariable函数。「设置环境变量」由于都是从windows.h库中跑,其实无所谓用什么函数。
        • 在Unix环境下,正常使用setenv和getenv即可。

        总结

        以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.cppcns.pythoncom)。

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        关注公众号