本文主要介紹了不同session之間的進(jìn)程通信暇屋。應(yīng)用場(chǎng)景如下:
SessionID為0的windows服務(wù)S需要檢測(cè)和控制SessionID為1或2(取決于該進(jìn)程的用戶)的UI程序的多個(gè)實(shí)例(P1 柏蘑,P2厅缺,P3……)的運(yùn)行狀態(tài)迎瞧。
1.遇到的第一個(gè)問題是捂龄,需要在服務(wù)S中檢測(cè)指定進(jìn)程P_i的運(yùn)行狀態(tài)躏哩。在該進(jìn)程為多實(shí)例運(yùn)行的狀態(tài)下奏候,區(qū)分不同的實(shí)例可以根據(jù)進(jìn)程ID和進(jìn)程的窗口名稱來進(jìn)行區(qū)分岩瘦。服務(wù)S接收到檢測(cè)窗口名稱為“WindowText_i”的進(jìn)程的運(yùn)行狀態(tài)未巫,而服務(wù)不能調(diào)用窗口相關(guān)的API來獲得進(jìn)程的窗口,只能遍歷到每個(gè)進(jìn)程的進(jìn)程ID担钮,因此無法將窗口名稱和進(jìn)程ID對(duì)應(yīng)起來橱赠。
解決方案:進(jìn)程P_i啟動(dòng)時(shí)創(chuàng)建一個(gè)共享內(nèi)存,該共享內(nèi)存的名稱的唯一性標(biāo)志為該進(jìn)程P_i的進(jìn)程ID箫津,然后進(jìn)程P_i向這塊共享內(nèi)存中寫入自己的窗口名稱;服務(wù)S需要檢測(cè)進(jìn)程P_i的狀態(tài)時(shí)狭姨,先枚舉所有的進(jìn)程ID,然后根據(jù)進(jìn)程ID查找共享內(nèi)存苏遥,如果找到饼拍,說明該進(jìn)程是自己需要的檢測(cè)的進(jìn)程,然后讀取共享內(nèi)存里的數(shù)據(jù)田炭,可以獲得該進(jìn)程的窗口名稱“WindowText_i”师抄,達(dá)到將窗口名稱和進(jìn)程ID對(duì)應(yīng)起來的目的。
共享內(nèi)存名稱示例:
'''
strMemName.Format(_T("Global\\ShareMemory_%d"),GetCurrentProcessId());
'''
前面加入的“global\\”是為了創(chuàng)建全局的共享內(nèi)存教硫,不同session區(qū)域的進(jìn)程只能夠找到自己所屬session區(qū)域的共享內(nèi)存和全局共享內(nèi)存叨吮。服務(wù)和進(jìn)程P_i屬于不同的session區(qū)域辆布,因此必須要加上全局標(biāo)識(shí)。
創(chuàng)建共享內(nèi)存的語句:
// 創(chuàng)建共享文件句柄
g_hMapFile = CreateFileMapping(
NULL,? // 物理文件句柄
&sa,? // 默認(rèn)安全級(jí)別
PAGE_EXECUTE_READWRITE,? // 可讀可寫
0,? // 高位文件大小
SHRE_MEMORY_BUF_SIZE,? // 低位文件大小
strMemName? // 共享內(nèi)存名稱
);
創(chuàng)建共享文件句柄的第二個(gè)參數(shù)為安全級(jí)別茶鉴,創(chuàng)建一般的共享內(nèi)存時(shí)锋玲,可以將這個(gè)參數(shù)設(shè)置為NULL,表示使用默認(rèn)的安全級(jí)別涵叮。但是在創(chuàng)建一個(gè)可以被服務(wù)獲取到的全局共享內(nèi)存時(shí)惭蹂,需要指定一個(gè)更加廣泛的的安全描述符,增加一個(gè)新的訪問控制項(xiàng)目(ACE)給你的ASP進(jìn)程的擁有者割粮,保證服務(wù)能夠獲取到該共享內(nèi)存盾碗。默認(rèn)的訪問控制列表(ACL)只包含創(chuàng)建者和管理員組。
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
sa.lpSecurityDescriptor = &sd;
創(chuàng)建全局的共享內(nèi)存之前舀瓢,最好再創(chuàng)建一個(gè)全局的互斥量廷雅,保證共享內(nèi)存不會(huì)被多個(gè)進(jìn)程同時(shí)訪問,增加安全性氢伟“窠危互斥量的命名模式與共享內(nèi)存一致,但是名字不能相同(相同的名字朵锣,創(chuàng)建時(shí)會(huì)失斆巍),創(chuàng)建互斥量時(shí)的安全描述符诚些,可以與共享內(nèi)存使用同一個(gè)飞傀。
共享內(nèi)存和互斥量的資源釋放都要在進(jìn)程P_i中實(shí)現(xiàn)。
2.遇到的第二個(gè)問題是诬烹,當(dāng)由服務(wù)S啟動(dòng)某個(gè)進(jìn)程P_i時(shí)砸烦,如果服務(wù)S使用的API為CreateProcesss,那么啟動(dòng)后的進(jìn)程P_i是看不到界面的绞吁,因?yàn)镃reateProcess創(chuàng)建的子進(jìn)程與父進(jìn)程的sessionID相同幢痘。如果想要?jiǎng)?chuàng)建有界面的進(jìn)程P_i,需要調(diào)用另一個(gè)API函數(shù):CreateProcessAsUser家破。調(diào)用過程如下:
BOOL WINAPI CreateProcessAsUser(
? _In_opt_? ? HANDLE? ? ? ? ? ? ? ? hToken,
? _In_opt_? ? LPCTSTR? ? ? ? ? ? ? lpApplicationName,
? _Inout_opt_ LPTSTR? ? ? ? ? ? ? ? lpCommandLine,
? _In_opt_? ? LPSECURITY_ATTRIBUTES lpProcessAttributes,
? _In_opt_? ? LPSECURITY_ATTRIBUTES lpThreadAttributes,
? _In_? ? ? ? BOOL? ? ? ? ? ? ? ? ? bInheritHandles,
? _In_? ? ? ? DWORD? ? ? ? ? ? ? ? dwCreationFlags,
? _In_opt_? ? LPVOID? ? ? ? ? ? ? ? lpEnvironment,
? _In_opt_? ? LPCTSTR? ? ? ? ? ? ? lpCurrentDirectory,
? _In_? ? ? ? LPSTARTUPINFO? ? ? ? lpStartupInfo,
? _Out_? ? ? LPPROCESS_INFORMATION lpProcessInformation
);
第一個(gè)參數(shù) hToken是用戶認(rèn)證令牌的句柄颜说,一般而言可以通過遍歷進(jìn)程,然后獲得資源管理器(explorer.exe)的用戶令牌來得到當(dāng)前用戶的用戶認(rèn)證令牌:
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,pe32.th32ProcessID);
bRet = OpenProcessToken(hProcess,TOKEN_ALL_ACCESS,&hToken);
其中汰聋,hProcess 為資源管理器(explorer.exe)的進(jìn)程句柄门粪。
但是在本例中,使用當(dāng)前登錄用戶的認(rèn)證令牌創(chuàng)建的進(jìn)程P_i是沒有權(quán)限創(chuàng)建全局共享內(nèi)存的烹困,因?yàn)橹挥芯哂泄芾韱T權(quán)限的用戶才可以創(chuàng)建全局共享內(nèi)存玄妈。普通進(jìn)程可以選擇右鍵管理員權(quán)限并單擊確認(rèn)按鈕啟動(dòng)程序來獲得管理員權(quán)限,但是本例中的進(jìn)程通過服務(wù)啟動(dòng),而服務(wù)通過網(wǎng)絡(luò)接收遠(yuǎn)程計(jì)算機(jī)的控制拟蜻,不可能在啟動(dòng)進(jìn)程P_i時(shí)再點(diǎn)擊確認(rèn)按鈕绎签。因此需要使用另一種方式為進(jìn)程P_i獲得管理員權(quán)限。以下代碼添加到服務(wù)中瞭郑。
第一辜御,需要獲得服務(wù)所屬用戶(system)的令牌hPToken:
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS,&hPToken);
第二,復(fù)制該令牌屈张,得到新的令牌hUserTokenDup
DuplicateTokenEx(hPToken,TOKEN_ALL_ACCESS,NULL,SecurityAnonymous, TokenPrimary,&hUserTokenDup);
第三,設(shè)置新的令牌hUserTokenDup的令牌信息?
dwConsoleSessionId = WTSGetActiveConsoleSessionId();//獲取當(dāng)前用戶的會(huì)話ID袱巨,如果不是遠(yuǎn)程桌面登錄的情況下阁谆,只執(zhí)行這一步就可以了
SetTokenInformation(hUserTokenDup,TokenSessionId,(void*)&dwConsoleSessionId,sizeof(DWOR));
當(dāng)操作系統(tǒng)上登錄了多個(gè)用戶的時(shí)候,WTSGetActiveConsoleSessionId()獲得的sessionid可能與當(dāng)前遠(yuǎn)程桌面用戶的不一致愉老,如果使用這個(gè)會(huì)話ID來設(shè)置令牌信息场绿,遠(yuǎn)程用戶無法看到啟動(dòng)后的進(jìn)程P_i的界面,因此需要獲得當(dāng)前資源管理器(explorer.exe)用戶的會(huì)話ID:ProcessIdToSessionId(pe32.th32ProcessID, &dwSessionID);然后比較兩個(gè)會(huì)話ID嫉入。
第四焰盗,為了保證進(jìn)程P_I具有創(chuàng)建全局共享內(nèi)存的權(quán)限,需要賦予令牌這個(gè)權(quán)限:
LookupPrivilegeValue(NULL,SE_CREATE_GLOBAL_NAME/*SE_DEBUG_NAME*/,&luid)咒林;//查找創(chuàng)建全局變量的權(quán)限
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;? ?
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //權(quán)限使能為啟用
AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES)NULL,NULL);
第五熬拒,創(chuàng)建進(jìn)程環(huán)境塊,保證環(huán)境塊是在用戶桌面的環(huán)境下
HANDLE hToken;
WTSQueryUserToken(dwConsoleSessionId, &hToken);
if(!CreateEnvironmentBlock(&pEnv,hToken,FALSE))
{
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
第六垫竞,創(chuàng)建進(jìn)程
CreateProcessAsUser(hUserTokenDup,NULL,lpCmdLine,NULL,NULL,FALSE, dwCreationFlags,pEnv,NULL,&si,&pi)澎粟;
第七,釋放資源
關(guān)閉前面所有的句柄欢瞪,并釋放環(huán)境:DestroyEnvironmentBlock(pEnv)活烙。
3.遇到的第三個(gè)問題,由服務(wù)創(chuàng)建的進(jìn)程P_i可以創(chuàng)建全局共享內(nèi)存遣鼓,但是進(jìn)程P_i有很多中啟動(dòng)方式啸盏,其他方式啟動(dòng)的進(jìn)程P_i不具有管理員權(quán)限,且用戶一般為當(dāng)前登錄用戶骑祟。這時(shí)候可以為該用戶添加一個(gè)創(chuàng)建全局變量的權(quán)限回懦。創(chuàng)建過程如圖所示。
按照序號(hào)1 - 6的順序曾我,依次點(diǎn)擊粉怕,然后在彈出的對(duì)話框中輸入需要添加的用戶名(以u(píng)ser為例),即可為該用戶添加創(chuàng)建全局對(duì)象的權(quán)限抒巢,完成后如序號(hào)7所示贫贝。
添加完成后,使用普通用戶也可以雙擊啟動(dòng)進(jìn)程P_i,并由進(jìn)程P_i創(chuàng)建全局共享內(nèi)存稚晚。