第一次做服务程序,碰到不少问题,稍作总结,留作后用。
程序结构
一个服务程序(A),每当有新用户登录,启动一个托盘(B),服务程序可以发数据给托盘,托盘也可以发数据给服务程序。因为二者都是在本机由此想到了使用共享内存。
新建两块共享内存,一块用作A写B读,另一块B写A读。因为server操作系统支持多用户登录,所以可能存在多个B程序。
A的大概代码:
//关于填充子进程命令行参数要多说一句,B写好数据之后要通知A来读,所以要发送一个消息给A,此时调用PostThreadMessage,第一个参数便是目的地的主线程ID
char szCmdLine[MAX_PATH];
sprintf_s(szCmdLine, MAX_PATH, "%ld",oGlobalConfig.dwMainThreadId
LPTSTR szCmdline = _tcsdup((LPCTSTR)szCmdLine);
//创建子进程,创建之后要保留子进程的主线程ID,从pi取得。这个也是A写完数据后发消息给B,通知B来读取共享内存的数据
if (!CreateProcessAsUser( pExplorerToken[i] ,strTrayName.c_str(), szCmdLine ,
NULL,NULL,FALSE , NORMAL_PRIORITY_CLASS| CREATE_NEW_CONSOLE ,NULL,NULL,&si,&pi ) )
{
printf("打开失败 %s\n",strTrayName.c_str());
}
//写完数据后通知B来取数据
bool WriteToShareMem(char *pTactics)
{
if (!hShareMem || !pShareMem || !pTactics)
return false;
//写入共享区
LPSEND_HEAD lpSendHead = (LPSEND_HEAD)pTactics;
memcpy(pShareMem, pTactics, lpSendHead->PacketLength);
//对vecChildThread加锁,因为当托盘程序退出时,需要从列表中清除对应ID
EnterCriticalSection(&oGlobalConfig.cs_ChildThread);
//写完后给托盘发送消息,托盘开始读数据
if (0 == oGlobalConfig.vecChildThread.size())
{
LeaveCriticalSection(&oGlobalConfig.cs_ChildThread);
return true;
}
//此向量里保存的全是子进程的主线程ID
vector<DWORD>::iterator iter = oGlobalConfig.vecChildThread.begin();
for (; iter != oGlobalConfig.vecChildThread.end(); ++iter)
{
BOOL bRet = PostThreadMessage(*iter, WM_TRAYGETDATA, 0, 0);
}
LeaveCriticalSection(&oGlobalConfig.cs_ChildThread);
return true;
}
至此,写数据完成,接下来是读取B写入的数据。这里用一个消息循环,来接收B发送的消息。这个消息循环一定要写在自winthread的主线程中,详见msdn
void AnalysisMsg()
{
MSG msg;
char *pMonitorData = NULL;//接收托盘发送的数据
while (GetMessage(&msg, NULL, WM_USER, WM_USER+3))
{
if (msg.message == WM_MONITORGETDATA)//自定义消息
{
//读取共享内存数据
pMonitorShare = (char *)MapViewOfFile(hMonitorShare, FILE_MAP_ALL_ACCESS, 0, 0, MONITOR_SHARE_SIZE);
if (NULL == pMonitorShare)
{
continue;
}
else
{
LPSEND_HEAD lpHead = (LPSEND_HEAD)pMonitorShare;
//判断操作类型
switch (lpHead->TransactionNumber)
{
case TRAY_APP_UDISK_REGISTER:
{
数据处理
}
break;
case TRAY_APP_UDISK_TACTICS:
{
数据处理
}
break;
}
}
}
}
}
B程序从命令行参数获取父进程的主线程ID
//通过命令行获取父进程的主线程ID
LPSTR lpstr = GetCommandLine();
string cmdline = lpstr;
m_uiParentThreadId = DWORD(atol(cmdline.c_str()));
其他与A程序相差不多,只是创建共享内存之类的改为open即可。有一点要注意,假如加入同步互斥控制,则需要用到EVENT或MUTEX之类,服务程序中创建的这些内核对象,在子进程中打开时会提示“拒绝访问”,错误代号:5。因为服务程序的安全级别较高,所以在创建的时候要传入第一个参数,而不是NULL。假如是用NULL创建的,在子进程也可以访问,只是在open中的第一个参数传入SYNCHRONIZE即可,但这时候获得的句柄不能对其做重置操作,例如:ResetEvent。此时如果有需求,还是在创建时传入安全属性,因我没这种需求,所以暂时没去研究这个安全属性怎样设置。
总结:
1、有名内核对象如果在服务程序中创建,则要注意安全属性
2、共享内存时,用消息通知的方式通知对方来读数据。读写要进行加锁操作,我用的是mutex