最近开发chrome项目中遇到了问题,chrome中的进程需要一些参数,之前的做法是写死在代码里头的,后来发现每次这些参数都会变化。然后每次一个小改动,构建出来一个包都需要2个多小时,效率非常低。所以想通过一个可配的方式来实现。
我们讨论最终决定使用环境变量来实现。调起chrome进程的业务进程在创建的时候创建环境变量,chrome进程使用这些变量。
讨论的时候还存在一个问题,环境变量对传递大数据可能不太使用,后面我确认了下确实是不能太大(具体后面讲解),不过目前项目遇到的问题是小数据,解决这个问题使用环境变量已经足够了,所以就先定了,先把问题解决了再说,后续再优化。
解决这个问题的前提是CreateProcess函数是可以把父进程的环境变量继承给子进程的,参数msdn中的说明:
lpEnvironment
A pointer to the environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process.
也就是说lpEnvironment参数传入NULL就可以了。
然后把msdn中关于GetEnvironmentVariable和SetEnvironmentVariable函数的说明先贴出来吧
BOOL SetEnvironmentVariable( LPCTSTR lpName, LPCTSTR lpValue );
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setenvironmentvariable
DWORD GetEnvironmentVariable( LPCTSTR lpName, LPTSTR lpBuffer, DWORD nSize );
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getenvironmentvariable
项目中使用的主要是在业务进程中设置环境变量的值,在chrome进程中读取环境变量的值,同时需要合老版本进行兼容,所以还
需要一个判断环境变量是否存在的功能。
设置环境变量:
SetEnvironmentVariable(_T("variableKeyA"), _T("variableValueA"));
这个函数还有一个功能,就是删除环境变量,把第二个参数传入NULL即可。
删除环境变量:SetEnvironmentVariable(_T("variableKeyA"), NULL);
读取环境变量:
需要说明的是我们事前并不知道环境变量字符串需要多大的缓存区,所以一般是先lpBuffer设置为NULL,nSize设置为0
函数会返回需要的缓冲区大小(包括字符串结尾的'\0'),然后我们知道大小了再去申请缓冲区。所以一般会调用两次,第一次得到
缓冲区的大小,第二次才得到真实的内容。(windows api 很多函数都是这样的 -_-)
DWORD value_length = ::GetEnvironmentVariable(variable_name, nullptr, 0);
if (value_length > 0)
{
wchar_t* buf = new wchar_t[value_length];
::GetEnvironmentVariable(variable_name, buf, value_length);
}
然后就是最后一个判断环境变量是否存在了。这个功能可以使用GetEnvironmentVariable函数来实现,主要体现在
该函数的返回值上。
msdn上的说明是这样,如果函数是否会返回0,然后没有环境变量GetLastError会是ERROR_ENVVAR_NOT_FOUND
If the function fails, the return value is zero. If the specified environment variable was not found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND.
我之前想的是函数返回0可能是不知道缓存区有多大,所以会先调用第一次,把lpBuffer设置为NULL,nSize设置为0后调用,得到缓存区大小。也就是说返回0并不能代表环境变量是否存在。所以我之前写的代码是这样子:
bool HasEnvironmentVar(const std::string& env_name)
{
if (env_name.empty())
return false;
::SetLastError(ERROR_SUCCESS);
DWORD dwSize = ::GetEnvironmentVariableA(env_name.c_str(), NULL, 0);
DWORD dwLastError = ::GetLastError();
if (0 == dwSize && ERROR_ENVVAR_NOT_FOUND == dwLastError)
return false;
return true;
}
首先说明这个函数是正确的,没有问题的,按照msdn上的说明来写的。
不过当我看到了chrome中的代码时我发现可以直接通过函数返回值判断就可以了。
我看的是chrome 75内核的代码:
代码路径:base\environment.cc
HasVar用来判断环境变量是否存在:
bool Environment::HasVar(StringPiece variable_name) {
return GetVar(variable_name, nullptr);
}
GetVar调用了GetVarImpl函数:
bool GetVarImpl(StringPiece variable_name, std::string* result) {
DWORD value_length =
::GetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), nullptr, 0);
if (value_length == 0)
return false;
if (result) {
std::unique_ptr<wchar_t[]> value(new wchar_t[value_length]);
::GetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), value.get(),
value_length);
*result = WideToUTF8(value.get());
}
return true;
}
也就是说当我们调用GetEnvironmentVariable函数,并且把lpBuffer设置为NULL,nSize设置为0后,如果环境变量
不存在,函数就返回0了。如果存在才是放回真实需要的缓冲区大小。
我本地验证了下果然是这样子。觉得chrome的代码还是又简洁有严谨。
特别需要注意的是SetEnvironmentVariable这个函数不允许多线程调用。开始我也是不太确定这个问题,我也是看到了chrome代码的注释才确定的。也就是说多线程调用的后果是不知道哪个设置成功了,最后的值是多少,或者说多线程调用后结果不可预期吧。
base\environment.cc 文件中 Environment 类的 SetVar 函数的注释,里头的实现就是调用的SetEnvironmentVariable函数。
// Returns true on success, otherwise returns false. This method should not be called in a multi-threaded process.
virtual bool SetVar(StringPiece variable_name, const std::string& new_value) = 0;
最后,之前提到的使用环境变量设置大块数据会有问题,我也查了下,确实是有限制。
lpValue
The contents of the environment variable. The maximum size of a user-defined environment variable is 32,767 characters. For more information, see Environment Variables.
Windows Server 2003 and Windows XP: The total size of the environment block for a process may not exceed 32,767 characters.
If this parameter is NULL, the variable is deleted from the current process's environment.
也就是说单个环境变量的值最大是32767个字符。虽然我本地验证了更多也是没问题的,但是还是按照官方的标准来吧。
总结:这篇文章主要是总结了window上环境变量的使用,方便以后遇到类似问题可以快速解决,不用再到处找资料了。
然后这个主题是讲解使用环境变量来解决这个问题的,不过存在大块数据不能使用的问题。这里如果要解决大块数据的
问题可以使用共享内存,可以参考之前写的一篇讲解共享内存的文章:https://blog.csdn.net/zsc_976529378/article/details/52604973
然后这个问题从大一点的高度来看就是通过IPC传送的问题。后面再单独写篇文章吧
附录:参考chrome代码自己实现的一个简易环境变量封装类。
class CEnvironmentVariable
{
public:
bool GetVar(const std::wstring& variable_name, std::wstring* result)
{
DWORD value_length =
::GetEnvironmentVariable(variable_name.c_str(), nullptr, 0);
if (value_length == 0)
return false;
if (result) {
std::unique_ptr<wchar_t[]> value(new wchar_t[value_length]);
::GetEnvironmentVariable(variable_name.c_str(), value.get(),
value_length);
*result = value.get();
}
return true;
}
// Syntactic sugar for GetVar(variable_name, nullptr);
bool HasVar(const std::wstring& variable_name)
{
return GetVar(variable_name, nullptr);
}
// Returns true on success, otherwise returns false. This method should not
// be called in a multi-threaded process.
bool SetVar(const std::wstring& variable_name,
const std::wstring& new_value)
{
return !!SetEnvironmentVariable(variable_name.c_str(), new_value.c_str());
}
// Returns true on success, otherwise returns false.
// This method should not be called in a multi-threaded process.
bool UnSetVar(const std::wstring& variable_name)
{
return !!SetEnvironmentVariable(variable_name.c_str(), nullptr);
}
};
测试代码:
CEnvironmentVariable envVar;
bool b = false;
b = envVar.HasVar(L"enable");
b = envVar.SetVar(L"enable", L"yes");
b = envVar.HasVar(L"enable");
b = envVar.UnSetVar(L"enable");
b = envVar.HasVar(L"enable");