最近做项目,windows系统的电脑中,布置一个OPC server,该server会去读取PLC的数据。然后电脑中再布置一个OPC客户端,该客户端通过读取本地OPC服务器的数据,间接的采集到PLC中的数据。然后就出现了问题:如果从命令行启动OPC客户端,那么可以读取数据,如果通过nssm安装OPC客户端,那么开机自启后,OPC客户端就读不到数据了。
经过组内讨论,觉得该问题可能会话ID造成的。因为如果OPC客户端通过nssm(或者sc)安装成服务,开机后以服务形式启动,那么会话ID为0,反之则为1,2等数据值。因为这个OPC客户端必须以开机服务的形式启动,因此如果要解决这个问题,可以在服务中以用户进程的形式去创建OPC客户端。创建用户进程使用CreateProcessAsUser。参考网上的案例,实现形式基本如下:
dwSessionID = WTSGetActiveConsoleSessionId();
if (FALSE == WTSQueryUserToken(dwSessionID, &hToken))
{
printf("WTSQueryUserToken fail,error code is %x\n", GetLastError());
bRet = false;
return bRet;
}
// 复制令牌
if (FALSE == DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary, &hDuplicatedToken))
{
printf("DuplicateTokenEx fail,error code is %x\n", GetLastError());
//目前不确定这步操作是否会影响创建进程
}
// 创建用户Session环境
if (FALSE == CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE))
{
printf("CreateEnvironmentBlock fail, error code is %x\n", GetLastError());
}
// 在复制的用户Session下执行应用程序,创建进程
if (FALSE == ::CreateProcessAsUser(hDuplicatedToken,
binPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT| CREATE_NO_WINDOW,
lpEnvironment, dir, &si, &pi))
{
printf("CreateProcessAsUser fail,error code is %x\n", GetLastError());
bRet = false;
return bRet;
}
else
{
printf("process handle is %d processId is %d\n",pi.hProcess,pi.dwProcessId);
}
实际使用过程中,发现获取token总是失败,错误代码是1008。查看MSDN上对于该接口函数的说明,说是要获取SE_TCB_NAME才可以。使能SE_TCB_NAME实现如下所示:
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCTSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValue(
NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid)) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
return TRUE;
}
之后再测试,发现还是有问题。原因在于现场的电脑是多用户系统,因此存在多个session,通过WTSGetActiveConsoleSessionId未必是那个真正有效的session,最后还是通过枚举素有session,然后选择state为Active的,作为后续真正使用的session。这样改代码后,服务启动后,创建的OPC客户端的会话ID不再是0。然后发现OPC客户端可以从服务端采集到数据了。