windows下服务无法读取OPC数据问题解决(通过服务创建用户进程)

最近做项目,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客户端可以从服务端采集到数据了。

 

你可能感兴趣的:(windows,c++,opc)