1. Introduction
PAL (Platform Adaptation Layer) 是Rotor得以在多种平台之间移植的关键所在。不同操作系统的API区别非常大,很难简单用#if/#else/#endif之类的语句进行条件编译来解决不同操作系统API之间互不兼容的问题,Rotor的解决方法是创建一个兼容层PAL,建立在PAL之上的程序无需关心面对的是何种操作系统,只需调用PAL提供的服务即可,在底层,PAL调用被转换成相应的操作系统相关的API调用,便可以解决此类问题。如果需要支持其他操作系统,只需为此操作系统写一个PAL实现,无需重写所有代码。Rotor的PAL设计和Win32 API十分接近,是Win32 API的一个子集,因此Win32 PAL的实现相对于*NIX版的PAL要简单很多,而对于*NIX来说,由于操作系统设计和Win32 API非常不同,需要做大量工作。PAL在架构上并无特别之处,只是简单的C函数,根据动态连接库实现的不同而不同,并没有使用通常C++所常用的Factory方式。
由于篇幅和个人精力有限,以后文章的的讨论只限于
Win32
版本PAL
实现。
PAL的主要组成部分如下:
DLL
|
Description
|
rotor_pal.dll
|
和平台相关的PAL的实现
|
rotor_palrt.dll
|
PAL Runtime,和平台无关的公共代码都在这里。比如加密/解密,资源,Assert,String等等
|
rotor_palrt_s.dll
|
静态GUID的定义
|
2. PAL_Initialization & PAL_Termination
PAL的Initialization/Termination是通过PAL_Initialize/PAL_Terminate来完成的。PAL_Initialize/PAL_Terminate只是PAL_Startup & PAL_Shutdown的一层Thin Wrapper,其主要作用在于维护一个PalReferenceCount,避免对PAL_Startup/PAL_Shutdown的重复调用。为了并发安全性,PalReferenceCount的++/--是通过InterlockedIncrement/InterlockedDecrement进行的。PAL_Initialize代码如下:
PALIMPORT
int
PALAPI
PAL_Initialize(
int argc,
char *argv[])
{
int RetVal = 0;
LONG RefCount;
RefCount = InterlockedIncrement(&PalReferenceCount);
if (RefCount == 1)
{
RetVal = PAL_Startup(argc, argv);
}
LOGAPI("PAL_Initialize(argc=0x%x, argv=%p)/n", argc, argv);
LOGAPI("PAL_Initialize returns int 0x%x/n", RetVal);
return RetVal;
}
|
PAL_Startup负责初始化PAL:
1. 首先PAL_Startup获得rotor_pal.dll的FullPathName:
Int
PALAPI
PAL_Startup(
int argc,
char *argv[])
{
int RetVal = -1;
HMODULE hMod;
WCHAR ModulePath[_MAX_PATH];
hMod = GetModuleHandleW(L"rotor_pal.dll");
if (!hMod) {
return RetVal;
}
if (!GetModuleFileNameW(hMod,
ModulePath,
sizeof(ModulePath)/sizeof(WCHAR))) {
return RetVal;
}
|
2. 设置ErrorMode,从而在无法打开文件的和CriticalError的情况下不显示错误对话框,直接返回错误。3. 调用RegisterWithEventLog在注册表中注册Rotor,这样RegisterEventSource才可以注册Rotor这个Source,ReportEvent才能工作。具体请参看MSDN。4. 调用InitializeObjecNameMangler初始化NameMangler。NameMangler的作用是根据rotor_pal.dll的所在位置用SHA1算法计算出一个Hash值,再转换为一个合法路径名。在很多需要名字的地方,如CreateEvent, CreateMutex…等均会将传入的名字进行Mangle处理,从而使得这些名字在不同的rotor_pal.dll实例中互不冲突。详情下面会有一节专门讲述。
SetErrorMode(SEM_NOOPENFILEERRORBOX|SEM_FAILCRITICALERRORS);
RegisterWithEventLog(ModulePath);
if (!InitializeObjectNameMangler(ModulePath)) {
return RetVal;
}
|
5. 接下来,仅当在调试版本中,根据PAL_API_TRACING的值设置LogFileHandle打开对PAL API的跟踪输出。PAL_API_TRACING 可以是stdout, stderr, debugger或者指定文件名,PAL通过PalLogApi, PalLogApiCore, PalAssertFired函数来调用WriteFile(LogFileHandle)或OutputDebugString(当PAL_API_TRACING的值为debugger时)来输出TRACE的内容。
#if DBG
//
// Do one-time initialization of the API tracing mechanism
//
{
DWORD dw;
WCHAR buffer[_MAX_PATH];
dw = GetEnvironmentVariableW(L"PAL_API_TRACING",
buffer,
sizeof(buffer)/sizeof(WCHAR));
if (dw != 0 && dw < sizeof(buffer)/sizeof(WCHAR)) {
// Environment variable exists and fit in the buffer
if (wcscmp(buffer, L"stdout") == 0) {
LogFileHandle = GetStdHandle(STD_OUTPUT_HANDLE);
} else if (wcscmp(buffer, L"stderr") == 0) {
LogFileHandle = GetStdHandle(STD_ERROR_HANDLE);
} else if (wcscmp(buffer, L"debugger") == 0) {
LogFileHandle = 0;
} else {
LogFileHandle = CreateFileW(buffer,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
}
}
#endif //DBG
|
6. 之后,安装PAL自己的Exception Filter,这样PAL便可以接管对Unhandled Exceptions的处理,下篇文章会详细解释7. 获得当前线程一个TLS(Thread Local Storage)的Slot,被SEH用到,同样,下篇文章会详细讲到8. 检查PAL_TRY_LOCAL_SIZE,在下篇文章会解释9. 设置浮点运算的控制字(Control Word)
// Note: MSVCRT has already registered their exception filter at this point
// SEH_CurrentTopLevelFilter won't be NULL
SEH_CurrentTopLevelFilter = (PAL_LPTOP_LEVEL_EXCEPTION_FILTER)
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)PAL_ExceptionFilter);
if ((SEH_Tls = TlsAlloc()) == TLS_OUT_OF_INDEXES) {
goto LExit;
}
PALASSERT(PAL_TRY_LOCAL_SIZE >= sizeof(jmp_buf));
// exceptions masked, round to nearest, 53 bit precision.
_controlfp(_MCW_EM | _RC_NEAR | _PC_53 | _IC_PROJECTIVE | _DN_SAVE,
_MCW_EM | _MCW_RC | _MCW_PC | _MCW_IC | _MCW_DN);
RetVal = 0;
LExit:
return RetVal;
}
|
PAL_Shutdown则是Shutdown PAL:
1. 释放NameManager所占用的内存和资源2. 释放SEH Tls所占用的TLS 的Slot,恢复之前的Exception Filter3. 在调试版本中,关闭LogFileHandle
void
PALAPI
PAL_Shutdown(
void)
{
PAL_LPTOP_LEVEL_EXCEPTION_FILTER PreviousFilter;
if (hCryptProv != NULL) {
CryptReleaseContext(hCryptProv, 0);
hCryptProv = NULL;
}
free(NameManglerA);
NameManglerA = NULL;
free(NameManglerW);
NameManglerW = NULL;
if (SEH_Tls != TLS_OUT_OF_INDEXES) {
TlsFree(SEH_Tls);
}
// Reset the global exception filter
PreviousFilter = (PAL_LPTOP_LEVEL_EXCEPTION_FILTER)
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)SEH_CurrentTopLevelFilter);
#if DBG
// Do this after the LOGAPI, so the output gets logged
if (LogFileHandle != INVALID_HANDLE_VALUE) {
//
// Refcount is zero and logging is enabled - close the
// file handle now in preparation for unload.
//
if (LogFileHandle) {
CloseHandle(LogFileHandle);
}
LogFileHandle = INVALID_HANDLE_VALUE;
}
#endif
}
|
3. Application Startup
下面以clix (Managed Application Launcher)为例,说明用到了PAL的程序的启动过程:
由于clix引用了rotor_pal.dll和rotor_palrt.dll,在clix的main执行之前,rotor_pal.dll和rotor_palrt.dll的DllMain会相继执行:
首先是rotor_pal.dll的DllMain被执行,当Dll在进程中被Load的时候 (DLL_PROCESS_ATTACH) 记录Dll的HINSTANCE,并调用DisableThreadLibraryCalls避免DllMain在线程创建 (DLL_THREAD_ATTACH) 和销毁 (DLL_THREAD_DETACH) 的时候被调用以提高速度:
// This is the main DLL entrypoint
BOOL
WINAPI
DllMain(
HANDLE hDllHandle,
DWORD dwReason,
LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH) {
hInstPal = (HINSTANCE)hDllHandle;
DisableThreadLibraryCalls(hDllHandle);
}
return TRUE;
}
|
之后,rotor_palrt.dll的DllMain被执行:
1. DLL_PROCESS_ATTACH的时候
a. 调用ROTOR_PAL_CTOR_TEST_RUN验证静态构造函数已经在DllMain之前执行,估计是部分编译器并没有遵循标准,有此宏可以更加容易定位此类错误。可参考rotor_palrt.h中ROTOR_PAL_CTOR_BODY和ROTOR_PAL_CTOR_TEST_RUN的定义
b. 调用PAL_Initialize,这是对PAL_Initialize的第一次调用,会调用PAL_Startup。注意argc和argv均传了NULL,这是因为在Win32的PAL实现中,PAL_Initialize不需要argc和argv。由于Unix/Linux实现下需要用到argc/argv来初始化一些信息,这些信息只能由外部传入得到,因此PAL_Initialize函数还是需要argc/argv这两个参数。
c. 为APP_DATA指针分配一个TLS (Thread Local Storage)的Slot g_itlsAppData, 这个Slot对于任何一个Thread都是一样的。APP_DATA的作用是保存CNumInfo的指针(和Thread相关的数字表示的设置,和culture对应起来)和IErrorInfo接口指针(记录错误信息)。
d. 调用InitNumInfo(TRUE),参数为TRUE表明是做初始化,FALSE表明则是释放,两种情况下InitNumInfo做的事情不同,TRUE则初始化CrticialSection,在CNumInfo内部用到。FALSE则释放CriticalSection和CNumInfo中的一些缓存信息,具体可以参考palrt/src/convert.cpp
extern "C"
BOOL WINAPI DllMain( HINSTANCE hInst, DWORD dwReason, LPVOID pvReserved )
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
ROTOR_PAL_CTOR_TEST_RUN(ROTOR_PALRT);
// initialize PAL
if (PAL_Initialize(NULL, NULL) != 0)
return FALSE;
// initialize APP_DATA
if ((g_itlsAppData = TlsAlloc()) == ITLS_EMPTY) {
return FALSE;
}
InitNumInfo(TRUE);
break;
|
2. DLL_PROCESS_DETACH
a. 调用ReleaseAppData释放g_itlsAppData对应的TLS的Slot中保存的APP_DATA指针
b. 调用InitNumInfo释放CNumInfo中的Cache
c. 调用PAL_Terminate
3. DLL_THREAD_DETACH的时候,调用ReleaseAppData释放和此线程相关的APP_DATA。
case DLL_PROCESS_DETACH:
ReleaseAppData();
InitNumInfo(FALSE);
if (g_itlsAppData != ITLS_EMPTY) {
TlsFree(g_itlsAppData);
}
PAL_Terminate();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
ReleaseAppData();
break;
}
return TRUE;
}
|
在rotor_pal.dll和rotor_palrt.dll的DllMain被执行完毕之后,palrt/palstartup.h中的main被调用。注意clix.cpp中的main并不是真正的main,main已经被定义成了PAL_startup_main,而真正的main则是在palstartup.h之中定义的:
int __cdecl main(int argc, char **argv) {
struct _mainargs mainargs;
#ifdef _MSC_VER
if (PAL_Initialize(0, NULL)) {
return 1;
}
#else
if (PAL_Initialize(argc, argv)) {
return 1;
}
#endif
// PAL_Terminate is a stdcall function, but it takes no parameters
// so the difference doesn't matter.
atexit((void (__cdecl *)(void)) PAL_Terminate);
mainargs.argc = argc;
mainargs.argv = argv;
exit((int)PAL_EntryPoint((PTHREAD_START_ROUTINE)run_main, &mainargs));
return 0; // Quiet a compiler warning
}
|
main作了如下几件事情:
1.初始化PAL2.调用atexit注册退出函数PAL_Terminate,这样PAL_Terminate便会在程序正常结束的时候被调用。注意虽然PAL_Terminate和atexit所要求的函数调用协定不同,PAL_Terminate是__stdcall而atexit参数要求__cdecl,由于PAL_Terminate是无参数的,__stdcall/__pascal在无参数的时候是没有区别的,因此可以直接强制转换成__cdecl3.调用PAL_EntryPoint入口函数,此函数调用run_main,并最后调用PAL_startup_main函数。如上所述,PAL_startup_main才是clix中的main4.退出时候,自动调用PAL_Terminate
OK,这次就分析到这里。下篇文章会重点分析PAL中的Exception机制。
作者: ATField
E-Mail: atfield_zhang@hotmail.com
Blog: http://blog.csdn.net/atfield