当前位置: 代码网 > it编程>编程语言>Java > 为什么获取环境变量getenv小心有坑

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

2025年03月26日 Java 我要评论
一、背景在工作中,所做的项目需要涉及两个不同语言( p/invoke)的信息传递。最后选定了一种环境变量的传递方式,但这也遇到了getenv带来的大坑!问题现象我们在c#的exe主流程中通过dllim

一、背景

在工作中,所做的项目需要涉及两个不同语言( 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 函数

  • environment.setenvironmentvariable()函数+ _wgetenv 函数

  • environment.setenvironmentvariable()函数+ getenvironmentvariable 函数

【dotnet构建的exe +msvc构建的dll】

  • environment.setenvironmentvariable()函数+ 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" {
    __declspec(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("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] == '=')
	return *ep + len + 1;
    }

  return null;
}
libc_hidden_def (getenv)

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

在继续搜索这个变量,发现其是在dll 首次加载时,crt(注意是g++的编译,而不是msvc) 会把「操作系统提供的环境变量,而不是进程环境」复制到自己的内存空间(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即可。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com