今天解决了我开发的FTL里面一个困扰我很久的Bug,把方法共享一下。
首先说明:这不是用 pragma_data 设置的在多个 Exe 公用同一个DLL时,使用DLL中相同的变量,而是同一个进程的EXE和各个DLL之间共享变量。
背景介绍:
FTL是本人参考ATL、WTL等开发的一个模版库,其中是我常用到的各种可重用的功能代码(比如日志、线程、线程池、流水线等)。因为全部都是头文件(.h 和内嵌的.hpp),在头文件中通过 __declspec(selectany) 定义了全局变量。但不同的 Exe、Dll 等模块包含了头文件后,会在各个模块内部定义各自的全局变量,从而造成逻辑错误。比如我使用了TLS 来将不同线程的日志写入不同的文件,尽量减少写日志时影响程序性能。因为前面的原因,造成同一个线程在 Exe 和 Dll 中写的日志会被判断成两个文件。
当然,因为大部分的人都不会编写模版库,而是编写DLL库,所以估计大部分人都用不到这种方式。这种方法只能是很难找到龙的屠龙技了
更改方法:通过文件映射的方式,将指定的变量设置为进程内全局。这样,不同的模块,包含同一个文件后,使用的变量就统一了。代码实现起来也比较简单。
以下的代码都写在头文件(.h) 中,业务代码包含头文件后直接使用 g_GlobalShareInfo.GetShareValue().xxxx 即可。
//在 Exe 和 DLL 中共享变量的内存区域 -- T 必须是简单类型,能支持 CopyMemory, sizeof 等操作
template<typename T>
class CFSharedVariable
{
public:
typedef BOOL (CALLBACK* InitializeSharedVariableProc)(T& rValue);
typedef BOOL (CALLBACK* FinalizeSharedVariableProc)(T& rValue);
//pszShareName 如果是NULL,会自动根据 Exe 的名字创建进程相关的共享区,这样同一进程中的各个模块能够共享变量
FTLINLINE CFSharedVariable(InitializeSharedVariableProc pInitializeProc,
FinalizeSharedVariableProc pFinalizeProc,
LPCTSTR pszShareName = NULL)
{
DWORD dwShareInfoSize = sizeof(T);
m_pShareValue = NULL;
m_hMapping = NULL;
m_bFirstCreate = FALSE;
m_pFinalizeProc = pFinalizeProc;
TCHAR szMapName[MAX_PATH] = {0};
if (NULL == pszShareName)
{
TCHAR szModuleFileName[MAX_PATH] = {0};
GetModuleFileName(NULL, szModuleFileName, _countof(szModuleFileName));
PathRemoveExtension(szModuleFileName);
LPCTSTR pszNamePos = PathFindFileName(szModuleFileName); // &szModuleFileName[nLength + 1];
StringCchPrintf(szMapName, _countof(szMapName), TEXT("FTLShare_%s_%d"), pszNamePos, GetCurrentProcessId());
pszShareName = szMapName;
}
m_hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
0, dwShareInfoSize, pszShareName);
FTLASSERT(m_hMapping != NULL);
if (m_hMapping)
{
m_bFirstCreate = (GetLastError() == 0); //Not ERROR_ALREADY_EXISTS
m_pShareValue = reinterpret_cast<T*>
(::MapViewOfFileEx(m_hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, dwShareInfoSize, NULL));
}
FTLASSERT(m_pShareValue);
if (m_bFirstCreate && pInitializeProc)
{
//如果是第一个定义该变量的模块,则初始化
if (m_pShareValue)
{
pInitializeProc(*m_pShareValue);
}
}
}
FTLINLINE ~CFSharedVariable()
{
if (m_pShareValue)
{
if (m_bFirstCreate && m_pFinalizeProc)
{
m_pFinalizeProc(*m_pShareValue);
}
UnmapViewOfFile(m_pShareValue);
m_pShareValue = NULL;
}
if (m_hMapping)
{
CloseHandle(m_hMapping);
m_hMapping = NULL;
}
}
FTLINLINE T& GetShareValue(){
FTLASSERT(m_pShareValue);
return *m_pShareValue;
}
private:
HANDLE m_hMapping;
BOOL m_bFirstCreate;
T* m_pShareValue;
FinalizeSharedVariableProc m_pFinalizeProc;
};
使用方式:先定义需要共享的结构体类型和初始化、终止化方法,然后通过 __declspec(selectany) 声明全局变量即可
struct FTLGlobalShareInfo
{
DWORD dwTraceTlsIndex; //FastTrace中保存线程局部储存的index
LONG nTraceSequenceNumber; //FastTrace中每一个日志的全局序列号(分析工具中排序用)
DWORD dwBlockElapseTlsIndex; //CFBlockElapse 中保存线程局部存储的Index
LONG nBlockElapseId; //CFBlockElapse 中保存Id
};
FTLINLINE BOOL CALLBACK _FtlGlobalShareInfoInitialize(FTLGlobalShareInfo& rShareInfo);
FTLINLINE BOOL CALLBACK _FtlGlobalShareInfoFinalize(FTLGlobalShareInfo& rShareInfo);
//定义FTL中会用到全局共享变量 -- 可以在使用FTL的 Exe/Dll 之间共享变量
__declspec(selectany) CFSharedVariable<FTLGlobalShareInfo> g_GlobalShareInfo(
_FtlGlobalShareInfoInitialize,
_FtlGlobalShareInfoFinalize,
NULL);
下面是初始化、终止化的代码:
BOOL CALLBACK _FtlGlobalShareInfoInitialize(FTLGlobalShareInfo& rShareInfo)
{
BOOL bRet = TRUE;
FTLASSERT(rShareInfo.dwTraceTlsIndex == 0);
FTLASSERT(rShareInfo.dwBlockElapseTlsIndex == 0);
rShareInfo.dwTraceTlsIndex = TlsAlloc();
FTLASSERT(TLS_OUT_OF_INDEXES != rShareInfo.dwTraceTlsIndex);
if (TLS_OUT_OF_INDEXES != rShareInfo.dwTraceTlsIndex)
{
TlsSetValue(rShareInfo.dwTraceTlsIndex, NULL);
}
rShareInfo.dwBlockElapseTlsIndex = TlsAlloc();
FTLASSERT(TLS_OUT_OF_INDEXES != rShareInfo.dwBlockElapseTlsIndex);
if (TLS_OUT_OF_INDEXES != rShareInfo.dwBlockElapseTlsIndex)
{
TlsSetValue(rShareInfo.dwBlockElapseTlsIndex, NULL);
}
rShareInfo.nTraceSequenceNumber = 0;
rShareInfo.nBlockElapseId = 0;
return bRet;
}
BOOL CALLBACK _FtlGlobalShareInfoFinalize(FTLGlobalShareInfo& rShareInfo)
{
if (TLS_OUT_OF_INDEXES != rShareInfo.dwTraceTlsIndex)
{
//CFFastTrace::CFTFileWriter*
FTLASSERT(NULL == TlsGetValue(rShareInfo.dwTraceTlsIndex)); //相关的资源必须已经释放
TlsFree(rShareInfo.dwTraceTlsIndex);
rShareInfo.dwTraceTlsIndex = TLS_OUT_OF_INDEXES;
}
if (TLS_OUT_OF_INDEXES != rShareInfo.dwBlockElapseTlsIndex)
{
//BlockElapseInfo*
FTLASSERT(NULL == TlsGetValue(rShareInfo.dwBlockElapseTlsIndex)); //相关的资源必须已经释放
TlsFree(rShareInfo.dwBlockElapseTlsIndex);
rShareInfo.dwBlockElapseTlsIndex = TLS_OUT_OF_INDEXES;
}
return TRUE;
}