為什么獲取環(huán)境變量getenv小心有坑
一、背景
在工作中,所做的項目需要涉及兩個不同語言( P/Invoke)的信息傳遞。最后選定了一種環(huán)境變量的傳遞方式,但這也遇到了getenv
帶來的大坑!
問題現(xiàn)象
我們在C#的exe主流程中通過DllImport,對環(huán)境變量進行了設(shè)置。隨后我通過DllImport
來引入C++的函數(shù)定義到托管函數(shù)內(nèi)存中,然后我再使用此環(huán)境變量時,發(fā)現(xiàn)在C++中根本不存在。
而當查看官方文檔對于Environment.SetEnvironmentVariable()
的(定義)[https://learn.microsoft.com/zh-cn/dotnet/api/system.environment.getenvironmentvariables?view=net-5.0&viewFallbackFrom=netstandard-1.0]時,可以發(fā)現(xiàn),其功能為:創(chuàng)建、修改或刪除存儲在當前進程中的環(huán)境變量。而通過DllImport
加載的C++代碼也明明是同一進程呀,為何會出現(xiàn)此種原因???
二、實驗
在C#中,設(shè)置環(huán)境變量基本就Environment.SetEnvironmentVariable()
一種方法,而在CPP中有三種方法:
- 標準庫方法:
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++測試的源代碼:
// 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#測試的源代碼:
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.
三、解釋
實驗表達了什么?
通過實驗可以發(fā)現(xiàn),凡是Windows.h庫定義的【獲取環(huán)境變量】的函數(shù)方法,都可以正常獲得。只有標準庫下面的getenv是獲得不了的。
但需要注意的是,msvc定義的標準庫getenv是可以獲得的!
因此,可以明確【g++下的標準庫實現(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這個字符指針數(shù)組來尋找對應(yīng)名稱的環(huán)境變量。
在繼續(xù)搜索這個變量,發(fā)現(xiàn)其是在DLL 首次加載時,CRT(注意是G++的編譯,而不是msvc) 會把「操作系統(tǒng)提供的環(huán)境變量,而不是進程環(huán)境」復(fù)制到自己的內(nèi)存空間(CRT的角度是之后這部分環(huán)境數(shù)據(jù)就與系統(tǒng)環(huán)境“斷開”了),從而完成該數(shù)組__environ的初始化,隨后的getenv就從該數(shù)組里拿。
由于SetEnvironmentVariable修改的是進程環(huán)境的環(huán)境變量,因此其兩者根本就是在對兩個副本環(huán)境變量(因為畢竟是進程級,不能影響系統(tǒng),因此是副本)在操作,所以不互通!
_putenv()小插曲
在搜索問題的過程中,發(fā)現(xiàn)有人說_putenv()設(shè)定的誰都可以獲得Environment.GetEnvironmentVariable()以及getenv()。實驗了一下,竟然發(fā)現(xiàn)真的可以!
但仔細看了引入其函數(shù)的頭文件,果不其然是windows.h!
于是,為什么 C++ 標準庫中只有 getenv() 而沒有 setenv()?
- 主要是因為各個操作系統(tǒng)對環(huán)境變量的實現(xiàn)和管理存在差異,因此,C++ 標準委員會在設(shè)計時,避免引入一個難以在所有系統(tǒng)上實現(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ù)?!冈O(shè)置環(huán)境變量」由于都是從windows.h庫中跑,其實無所謂用什么函數(shù)。
- 在Unix環(huán)境下,正常使用setenv和getenv即可。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
一文帶你深入了解Qt中的順序容器類與關(guān)聯(lián)容器類
Qt中也有很多容器類,他們在存取速度、內(nèi)存開銷等方面進行了優(yōu)化,使用起來更輕量級、更便捷,下面就跟隨小編一起來學(xué)習(xí)一下它們的具體使用吧2024-04-04c語言中字符串函數(shù)(庫函數(shù)使用)和模擬實現(xiàn)圖文教程
C語言中對字符和字符串的處理很是頻繁,但是C語言本身并沒有字符串類型,這篇文章主要給大家介紹了關(guān)于c語言中字符串函數(shù)(庫函數(shù)使用)和模擬實現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-01-01C數(shù)據(jù)結(jié)構(gòu)之雙鏈表詳細示例分析
以下是對c語言中的雙鏈表進行了詳細的分析介紹,需要的朋友可以過來參考下2013-08-08