為什么獲取環(huán)境變量getenv小心有坑
一、背景
在工作中,所做的項(xiàng)目需要涉及兩個(gè)不同語言( P/Invoke)的信息傳遞。最后選定了一種環(huán)境變量的傳遞方式,但這也遇到了getenv帶來的大坑!
問題現(xiàn)象
我們?cè)贑#的exe主流程中通過DllImport,對(duì)環(huán)境變量進(jìn)行了設(shè)置。隨后我通過DllImport來引入C++的函數(shù)定義到托管函數(shù)內(nèi)存中,然后我再使用此環(huán)境變量時(shí),發(fā)現(xiàn)在C++中根本不存在。
而當(dāng)查看官方文檔對(duì)于Environment.SetEnvironmentVariable()的(定義)[https://learn.microsoft.com/zh-cn/dotnet/api/system.environment.getenvironmentvariables?view=net-5.0&viewFallbackFrom=netstandard-1.0]時(shí),可以發(fā)現(xiàn),其功能為:創(chuàng)建、修改或刪除存儲(chǔ)在當(dāng)前進(jìn)程中的環(huán)境變量。而通過DllImport加載的C++代碼也明明是同一進(jìn)程呀,為何會(huì)出現(xiàn)此種原因???
二、實(shí)驗(yàn)
在C#中,設(shè)置環(huán)境變量基本就Environment.SetEnvironmentVariable()一種方法,而在CPP中有三種方法:
- 標(biāo)準(zhǔn)庫方法:
getenv函數(shù) - Windows.h庫方法:
_wgetenv函數(shù)以及GetEnvironmentVariable函數(shù)
首先,先說結(jié)果:
【dotnet構(gòu)建的EXE + MingW構(gòu)建的DLL】
Environment.SetEnvironmentVariable()函數(shù)+getenv函數(shù)

Environment.SetEnvironmentVariable()函數(shù)+_wgetenv函數(shù)

Environment.SetEnvironmentVariable()函數(shù)+GetEnvironmentVariable函數(shù)

【dotnet構(gòu)建的EXE +MSVC構(gòu)建的DLL】
Environment.SetEnvironmentVariable()函數(shù)+getenv函數(shù)

下面是我們的測(cè)試代碼:
- C++測(cè)試的源代碼:
// getenv函數(shù)所需頭文件
#include <cstdlib>
#include <iostream>
// _wgetenv函數(shù)所需頭文件
#include <cwchar>
#include <string>
// GetEnvironmentVariable函數(shù)所需頭文件
#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#測(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++代碼構(gòu)建的編譯代碼:
@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.
三、解釋
實(shí)驗(yàn)表達(dá)了什么?
通過實(shí)驗(yàn)可以發(fā)現(xiàn),凡是Windows.h庫定義的【獲取環(huán)境變量】的函數(shù)方法,都可以正常獲得。只有標(biāo)準(zhǔn)庫下面的getenv是獲得不了的。
但需要注意的是,msvc定義的標(biāo)準(zhǔn)庫getenv是可以獲得的!
因此,可以明確【g++下的標(biāo)準(zhǔn)庫實(shí)現(xiàn)是可能存在問題的】。
G++下的getenv為什么獲得不了環(huán)境變量?
我先去看了一下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)
代碼可以看出,該環(huán)境變量的獲取本質(zhì)就是循環(huán)__environ這個(gè)字符指針數(shù)組來尋找對(duì)應(yīng)名稱的環(huán)境變量。
在繼續(xù)搜索這個(gè)變量,發(fā)現(xiàn)其是在DLL 首次加載時(shí),CRT(注意是G++的編譯,而不是msvc) 會(huì)把「操作系統(tǒng)提供的環(huán)境變量,而不是進(jìn)程環(huán)境」復(fù)制到自己的內(nèi)存空間(CRT的角度是之后這部分環(huán)境數(shù)據(jù)就與系統(tǒng)環(huán)境“斷開”了),從而完成該數(shù)組__environ的初始化,隨后的getenv就從該數(shù)組里拿。
由于SetEnvironmentVariable修改的是進(jìn)程環(huán)境的環(huán)境變量,因此其兩者根本就是在對(duì)兩個(gè)副本環(huán)境變量(因?yàn)楫吘故沁M(jìn)程級(jí),不能影響系統(tǒng),因此是副本)在操作,所以不互通!
_putenv()小插曲
在搜索問題的過程中,發(fā)現(xiàn)有人說_putenv()設(shè)定的誰都可以獲得Environment.GetEnvironmentVariable()以及getenv()。實(shí)驗(yàn)了一下,竟然發(fā)現(xiàn)真的可以!
但仔細(xì)看了引入其函數(shù)的頭文件,果不其然是windows.h!
于是,為什么 C++ 標(biāo)準(zhǔn)庫中只有 getenv() 而沒有 setenv()?
- 主要是因?yàn)楦鱾€(gè)操作系統(tǒng)對(duì)環(huán)境變量的實(shí)現(xiàn)和管理存在差異,因此,C++ 標(biāo)準(zhǔn)委員會(huì)在設(shè)計(jì)時(shí),避免引入一個(gè)難以在所有系統(tǒng)上實(shí)現(xiàn)一致行為的功能。
- 在 POSIX 系統(tǒng)(如 Linux 和 macOS)上,通??梢允褂?setenv() 或 putenv() 來設(shè)置環(huán)境變量
- 但在 Windows 上,管理環(huán)境變量的方式有所不同(如使用 SetEnvironmentVariable())。
四、啟發(fā)
如何在P/Invoke中使用【獲取和修改環(huán)境變量】
- 在Win環(huán)境下,「獲取環(huán)境變量」還是避免使用getenv,統(tǒng)一使用windows.h下的庫函數(shù)如_putenv()、GetEnvironmentVariable函數(shù)。「設(shè)置環(huán)境變量」由于都是從windows.h庫中跑,其實(shí)無所謂用什么函數(shù)。
- 在Unix環(huán)境下,正常使用setenv和getenv即可。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
QT Creator+OpenCV實(shí)現(xiàn)圖像灰度化的示例代碼
這篇文章主要為大家詳細(xì)介紹了QT如何利用Creator和OpenCV實(shí)現(xiàn)圖像灰度化效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-12-12
C語言實(shí)現(xiàn)簡(jiǎn)單停車場(chǎng)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡(jiǎn)單停車場(chǎng)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
內(nèi)聯(lián)函數(shù)inline與宏定義深入解析
類的內(nèi)斂函數(shù)是一個(gè)真正的函數(shù)。使用內(nèi)聯(lián)函數(shù)inline可以完全取代表達(dá)式形式的宏定義2013-09-09
一篇文章教你如何用C語言實(shí)現(xiàn)strcpy和strlen
這篇文章主要為大家介紹了C語言實(shí)現(xiàn)strcpy和strlen的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
vscode+qt+qmake開發(fā)環(huán)境搭建(最全最詳細(xì))
本文主要介紹了vscode+qt+qmake開發(fā)環(huán)境搭建,文中通過圖文示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04

