大家都知道程序中若要使用COM組件則必需要先調用CoInitialize,該函數主要是用來初始化COM執行環境。但這個函數的作用域是以線程為單位還是以進程為單位呢?或許大家已經通過測試程序摸索出答案,沒錯,是以線程為單位。今天我們就略微再深入一下,通過分析CoInitialize的詳細實現來印證我們的想法。
我們先來看看CoInitialize的匯編
769B2A24 mov edi, edi
769B2A26 push ebp
769B2A27 mov ebp, esp
769B2A29 push 2 ; dwCoInit
769B2A2B push [ebp+8] ; pvReserved
769B2A2E call _CoInitializeEx@8 ; CoInitializeEx(x,x)
769B2A33 pop ebp
769B2A34 retn 4
能夠看到,當中的實現還是比較簡單的,它僅僅是簡單地調用了CoInitializeEx,將第二個參數設置為2,即COINIT_APARTMENTTHREADED。我們再來看看CoInitializeEx的實現
769AEF5B mov edi, edi
769AEF5D push ebp
769AEF5E mov ebp, esp
769AEF60 push ecx
769AEF61 push ebx
769AEF62 mov ebx, [ebp+0C]
769AEF65 mov eax, ebx
769AEF67 and eax, 0Eh ; 檢查參數是否正確,眼下第二個參數僅僅用了一個字節
769AEF6A cmp eax, ebx
769AEF6C jnz loc_76A0B8C7
769AEF72 push edi
769AEF73 xor edi, edi
769AEF75 cmp [ebp+8], edi ; 推斷第一個參數是否為NULL
769AEF78 jnz loc_76A0B8D1
769AEF7E
769AEF7Eloc_769AEF7E:
769AEF7E call ?IsRunningInRPCSS@@YGHXZ ;IsRunningInRPCSS(void)
769AEF83 test eax, eax ;推斷當前進程是否是RPCSS
769AEF85 jnz loc_76A0B8ED ;假設是(即返回非0)則返回“災難性故障”的錯誤
769AEF8B mov eax, large fs:18h
769AEF91 mov eax, [eax+0F80h]
769AEF97 cmp eax, edi
769AEF99 mov [ebp+8], eax
769AEF9C jz loc_769ADF26 ; 推斷當前線程中的struct tagSOleTlsData結構體是否分配,若未分配則進行分配
769AEFA2
769AEFA2loc_769AEFA2:
769AEFA2 push esi
769AEFA3 push edi ; __int32
769AEFA4 push ebx ; unsigned __int32
769AEFA5 xor esi, esi
769AEFA7 inc esi
769AEFA8 push esi ; int
769AEFA9 push esi ; int
769AEFAA call ?NotifyInitializeSpies@@YGJHHKJ@Z ;NotifyInitializeSpies(int,int,ulong,long)
769AEFAF call ?IsThreadInNTA@@YGHXZ ; IsThreadInNTA(void)
769AEFB4 test eax, eax
769AEFB6 jnz loc_769DAFAD ; 假設是 則返回“無法在設置線程模式后對其加以更改。”的錯誤
769AEFBC mov eax, [ebp+8]
769AEFBF mov ecx, [eax+0Ch]
769AEFC2 test ch, 10h ;推斷標識第4位(從第0位開始)是否置位
769AEFC5 jnz loc_769D9D20 ; server出現意外情況。
769AEFCB mov edx, ebx
769AEFCD and edx, 2
769AEFD0 mov [ebp-4], edx
769AEFD3 jz short loc_769AEFDE ; 非COINIT_APARTMENTTHREADED模式
769AEFD5 test ch, 1 ;推斷標識第0位是否置位
769AEFD8 jnz loc_769DAFAD ; 返回“無法在設置線程模式后對其加以更改。”的錯誤
769AEFDE
769AEFDEloc_769AEFDE:
769AEFDE cmp edx, edi
769AEFE0 jz loc_769DAFA5 ; 非COINIT_APARTMENTTHREADED模式
769AEFE6
769AEFE6loc_769AEFE6:
769AEFE6 test bl, 8
769AEFE9 jnz loc_76A0B901 ;第二個參數中COINIT_SPEED_OVER_MEMORY標識位被設置,即為單線程套件
769AEFEF
769AEFEFloc_769AEFEF:
769AEFEF add eax, 18h
769AEFF2 inc dword ptr [eax] ; tagSOleTlsData.dwReserved1[0]++;
769AEFF4 cmp [eax], esi
769AEFF6 jnz loc_769ADBF9 ; 推斷tagSOleTlsData.dwReserved1[0]==1?
769AEFFC test edx, edx
769AEFFE mov ebx, offset?gMTAInitLock@@3VCOleStaticMutexSem@@A ; COleStaticMutexSem gMTAInitLock
769AF003 jz loc_769DAFF2 ; 第二個參數未設置COINIT_APARTMENTTHREADED標識,即為多線程套件
769AF009
769AF009loc_769AF009:
769AF009 mov esi, offset?g_mxsSingleThreadOle@@3VCOleStaticMutexSem@@A ; COleStaticMutexSemg_mxsSingleThreadOle
769AF00E mov ecx, esi
769AF010 call ?Request@COleStaticMutexSem@@QAEXXZ ;COleStaticMutexSem::Request(void)
769AF015 push [ebp+0C]
769AF018 lea eax, [ebp+8]
769AF01B push eax
769AF01C call ?wCoInitializeEx@@YGJAAVCOleTls@@K@Z ;wCoInitializeEx(COleTls &,ulong) 調用wCoInitializeEx
769AF021 mov ecx, esi
769AF023 mov edi, eax
769AF025 call ?Release@COleStaticMutexSem@@QAEXXZ ;COleStaticMutexSem::Release(void)
769AF02A test edi, edi
769AF02C jl loc_76A0B90C
769AF032
769AF032loc_769AF032:
769AF032 cmp [ebp-4], 0
769AF036 jz loc_769DB004 ; 第二個參數未設置COINIT_APARTMENTTHREADED標識,即為多線程套件
769AF03C
769AF03C loc_769AF03C: ; CODE XREF:CoInitializeEx(x,x)+2C0B6j
769AF03C xor esi, esi
769AF03E inc esi
769AF03F
769AF03F loc_769AF03F:
769AF03F push edi ; __int32
769AF040 push [ebp+0C] ; unsigned__int32
769AF043 push 0 ; int
769AF045 push esi ; int
769AF046 call ?NotifyInitializeSpies@@YGJHHKJ@Z ;NotifyInitializeSpies(int,int,ulong,long)
769AF04B pop esi
769AF04C
769AF04C loc_769AF04C:
769AF04C pop edi
769AF04D
769AF04Dloc_769AF04D:
769AF04D pop ebx
769AF04E leave
769AF04F retn 8
當中有幾點請注意:
1、在第一個參數為非空時,該函數會推斷當前進程是否為EXCEL;
2、該函數也會推斷當前進程是否為RPCSS,該進程的用途請大家另行查閱;檢查進程是否為RPCSS的方法主要是:先推斷當前進程是否有加載Windows文件夾下\\system32\\rpcss.dll,假設未加載則當前進程不是RPCSS;若加載了,則獲取該DLL中名為WhichService的導出函數,假設未找到該函數也覺得當前進程是RPCSS;若找到,並該函數的返回值大於等於0,且作為該函數參數的指針所指向的值為2則當前進程不是RPCSS,否則當前進程即為RPCSS。
3、每一個線程的TEB結構向后偏移0x0F80的地方存放struct tagSOleTlsData的指針,該結構的聲明例如以下:
typedef structtagSOleTlsData
{
void *pvReserved0[2];
DWORD dwReserved0[3];
void *pvReserved1[1];
DWORD dwReserved1[3];
void *pvReserved2[4];
DWORD dwReserved2[1];
void *pCurrentCtx;
} SOleTlsData;
該結構中存放了當前線程有關COM的環境信息,這個結構體中各個域的定義微軟貌似沒有公開。線程啟動后,在沒有該線程調用CoInitialize或CoInitializeEx之前,該指針為空。第一次調用上述函數后,為該線程從堆上分配該結構的內存並將其指針保存至TEB+0x0F80處。
4、我們注意到,全部對struct tagSOleTlsData內容的改動都未進行相互排斥保護,這是由於全部對該結構的改動操作都在當前線程內部進行,因此也就不存在多線程同步的問題;而對於一些全局信息的改動則都進行了保護。