获取环境变量 getenv小心有坑!

一、背景

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

1.1 问题现象

我们在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 offif exist build (echo Build folder already exists. Deleting...rmdir /s /q build
)echo Build folder create..
mkdir buildecho Running CMake configuration...
cmake -B build -DCMAKE_CXX_COMPILER=g++ -G Ninjaecho Building the project...
cmake --build buildecho Build completed.

三、解释

3.1 实验表达了什么?

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

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

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

3.2 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修改的是进程环境的环境变量,因此其两者根本就是在对两个副本环境变量(因为毕竟是进程级,不能影响系统,因此是副本)在操作,所以不互通!

3.3 _putenv()小插曲

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

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

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

  • 主要是因为各个操作系统对环境变量的实现和管理存在差异,因此,C++ 标准委员会在设计时,避免引入一个难以在所有系统上实现一致行为的功能。

  • 在 POSIX 系统(如 Linux 和 macOS)上,通常可以使用 setenv()putenv() 来设置环境变量

  • 但在 Windows 上,管理环境变量的方式有所不同(如使用 SetEnvironmentVariable())。

四、启发

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/431.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

告别登录,这款插件直接复制CSDN内容,真棒!

前言 我们在开发过程中&#xff0c;肯定会遇到这样或者那样的问题&#xff0c;这时候&#xff0c;我们想到最多的就是用搜索引擎去搜索各种资料&#xff0c;查看各种博客。以前&#xff0c;查看博客是很方便的&#xff0c;不过后来&#xff0c;像CSDN的&#xff0c;就是必须要…

为Meta Spark准备3D模型

有许多工具可以帮助你为 Meta Spark Studio 创建 3D 对象&#xff0c;包括 Cinema4D、Blender 和 3ds Max。你还可以使用 Meta Spark Toolkit 优化 Blender 对象。 在本指南中&#xff0c;我们将介绍正确的设置&#xff0c;以便你可以成功地为 Meta Spark Studio 准备对象&…

poi处理excel文档时,与lombok的@Accessors(chain = true)注解冲突

poi在反射封装数据时会判断set方法的返回是不是Void&#xff0c;加上Accessors会造成NoSuchMethodException异常

bash: git: command not found

在windows上重新安装Git之后&#xff0c;遇到cmd可以使用git命令&#xff0c;但是git bash中使用的git命令的时候&#xff0c;会提示&#xff1a; $ git bash: git: command not found 解决办法 找到用户目录下的.bash_profile和.bashrc文件&#xff0c;编辑打开&#xff0c;找…

DFA算法实现敏感词过滤

DFA算法实现敏感词过滤 需求&#xff1a;检测一段文本中是否含有敏感词。 比如检测一段文本中是否含有&#xff1a;“滚蛋”&#xff0c;“滚蛋吧你”&#xff0c;“有病”&#xff0c; 可使用的方法有&#xff1a; 遍历敏感词&#xff0c;判断文本中是否含有这个敏感词。 …

Java 正则表达式一口气讲完!b( ̄▽ ̄)d

Java 正则表达式元字符 Java正则表达式教程 - Java正则表达式元字符 元字符是在Java正则表达式中具有特殊含义的字符。 Java中的正则表达式支持的元字符如下: ( ) [ ] { } \ ^ $ | ? * . < > - ! 字符类 元字符 [和] 指定正则表达式中的字符类。 字符类是一组字…

(实战)WebApi第10讲:Swagger配置、RESTful与路由重载

一、Swagger配置 1、导入SwashBuckle.AspNetCore包 2、在.NET Core 5框架里的startup.cs文件里配置swagger 3、在.NET Core 6框架里的Program.cs文件里配置swagger 二、RESTful风格&#xff1a;路由重载&#xff0c;HttpGet()括号中加参数 &#xff08;1&#xff09;原则&…

【进阶sql】复杂sql收集及解析【mysql】

开发时会出现&#xff0c;必须写一些较复杂sql的场景 可能是给会sql的客户 提供一些统计sql 或是临时需要统计数据信息但是 开发一个统计功能有来不及的情况 也可能是报表系统组件 只支持 sql统计的情况 特地记录下这些sql 作为积累 substring 截取查询出的字符串&#xff…

心情追忆-AI分析报错

之前我独自开发了一个名为“心情追忆”的小程序&#xff0c;旨在帮助用户记录日常的心情变化及重要时刻。 项目需求来源->设计->前端(小程序)->后端->部署均由我一人完成. 上线一个月. 通过群聊分享等. 用户量也有了100多人. 我希望持续发展 由于项目的开发与测试…

Leetcode21:合并两个有效链表

原题地址&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示…

【Python · Pytorch】人工神经网络 ANN(中)

【Python Pytorch】人工神经网络 ANN&#xff08;中&#xff09; 6. 反向传播6.1 梯度下降法6.1.1 线搜索方法6.1.2 微分 & 导数6.1.3 偏导数6.1.4 Jacobian矩阵6.1.5 梯度 & 梯度下降法按维度介绍 6.1.6 面临挑战平原现象 & 振荡现象局部最小值鞍点梯度消失梯度爆…

时间序列预测(十)——长短期记忆网络(LSTM)

目录 一、LSTM结构 二、LSTM 核心思想 三、LSTM分步演练 &#xff08;一&#xff09;初始化 1、权重和偏置初始化 2、初始细胞状态和隐藏状态初始化 &#xff08;二&#xff09;前向传播 1、遗忘门计算&#xff08;决定从上一时刻隐状态中丢弃多少信息&#xff09; 2、…

基于springboot+小程序的汽车销售管理系统(汽车4)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 ​ 1、管理员实现了首页、个人中心、管理员管理、基础数据管理、论坛管理、公告信息管理、汽车管理、用户管理、轮播图信息等。 ​ 2、用户实现了注册、登录、首页、汽车类型、论坛、购物…

C++ | Leetcode C++题解之第522题最长特殊序列II

题目&#xff1a; 题解&#xff1a; class Solution { public:int findLUSlength(vector<string>& strs) {auto is_subseq [](const string& s, const string& t) -> bool {int pt_s 0, pt_t 0;while (pt_s < s.size() && pt_t < t.siz…

Android Preference浅析(设置Setting)

各位&#xff0c;好久不见&#xff0c;最近时间较为充裕&#xff0c;更新一下博客。 本篇在我的理解、认识范围内&#xff0c;讲述一下Android中的Preference&#xff08;破粉斯~&#xff09;这玩意&#xff0c;常用于项目中的设置模块中。在工作中我也主要负责了设置模块相关…

双指针问题解法集(一)

1.指针对撞问题&#xff08;利用有序数组的单调性衍生&#xff09; 1.1LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09;​​​​​​ 1.1.1题目解析 有序数组&#xff0c;找到和为price的两个元素&#xff0c;只需要一个解即可。 1.1.2算法…

根据提交的二维数据得到mysql建表和插入数据实用工具

根据提交的二维数据得到mysql建表和插入数据实用工具,这是重构版本(之前有过)。 会通过数据的长度&#xff0c;类型&#xff0c;是否数字&#xff0c;是否唯一等做判断&#xff0c;且每千条一个插入语句以优化性能。 <?php //整理与分享&#xff1a;yujianyue<1505859…

数据库连接池实现

目录 前提&#xff1a;如果我要操作多个表&#xff0c;那么就会产生冗余的JDBC步骤&#xff0c;另一个弊端就是每次都需要数据库连接对象&#xff08;Connection&#xff09;&#xff0c;获取效率低下&#xff0c;每次使用时都需要先进行连接 数据库连接池的特点&#xff1a; …

JavaEE初阶------网络编程续+传输层UDP协议介绍

文章目录 1.实现翻译服务器2.TCP的socket api使用3.初识网络编程3.1开发中常见的格式3.1.1行文本方式构造3.1.2xml格式表示3.1.3json处理格式3.1.4protobuffer格式 3.2传输层3.2.1UDP报文格式3.2.2校验和的说明3.2.3校验和的计算方法3.2.3.1CRC算法3.2.3.2MD5算法 1.实现翻译服…

python的数据结构列表方法及扩展(栈和队列)

python的数据结构 python的list方法 list.append() 添加一个元素到列表末尾。list,append(num)相当于a[len(a):] [num] a [1,2,3,4,5] a.append(6) print(a) a[len(a):] [7] print(a)list.extend() 添加指定列表的所有元素。list.extend(nums)相当于a a nums a [1,2,3]…