前言

装了和谐版的 cadence SPB17.4, 用的挺好。
有个小瑕疵,服务中的"Cadence License Manager", 不管怎么设置,都有可能在开机后启动不起来(在我的本本上表现,就是有时开机后服务是运行状态,有时是停止状态)。

 
也不是完全不好使,是有时好使,有时不好使。大部分情况下都不好使,桑心啊。

原因有可能是Cadence License Manager 依赖网络服务或其他基础服务,可能服务开机就启动,那时网络状况不佳,或者就没有网络连接呢,导致服务启动不起来。

一般,开机后,运行cpture或editor失败,才会想起可能Cadence License Manager服务没起来。

自己又想不起来,每次开机后,去手工启动服务。
万一养成这个习惯,那不就是强迫症了。

今天找到一个保证Cadence License Manager 能100%自动启动成功的解决方法。

思路

  • 写一个守护程序(我用vs2022 + vc++ + console弄的),一直转圈检测 cadence服务的状态,每1分钟就检测一次,如果服务不是运行状态,就尝试用程序自动启动一次服务。
  • 建立一个计划任务,登录后30秒,将守护程序运行起来。
    用计划任务而不是将守护程序直接丢到win10开机启动文件夹中的原因是:守护程序因为要检测服务运行状态,启动服务,这些都需要管理员权限。

经过实验,放到开机启动文件夹中,不好使,具体原因未知,因为没写那么细致(e.g. 记录一下文件日志,记录程序退出原因,还是根本就启动不起来,因为没有看见UAC权限弹框),就是给自己用的一个小程序,自己能接受就是全部。

将守护程序放到计划任务中时,就可以指定程序运行的权限和启动时机,可以保证将守护程序自动运行起来.

实现

守护程序实验

环境 :vs2022 + vc++ + console + 服务操作API
260行的小工程。

// @file prj_demons_by_service_name.cpp
// @brief 查询指定的服务是否已经启动,如果没启动,则启动服务。如果服务已经启动,就退出
// 将这个程序(或快捷方式)放入win10的启动文件夹中, 用来确保会启动cadence的服务
// 因为我发现cadence的这个服务,无论怎么设置,开机后都不保证能确定启动起来. 估计是啥依赖环境没满足引起没启动或启动失败。

// 找win10的启动文件夹位置
// 开始菜单输入run, 选中run程序
// 输入 shell:startup 回车,转到win10启动文件夹中
// 我本机的win10启动文件夹为 C:\Users\my_name\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

// @note
// dev env = vs2022 vc++ console

#include "pch.h"

#include <windows.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <tchar.h>

#include <strsafe.h>

#include <locale.h>
#include <conio.h>

#define DST_SERVICE_NAME TEXT("Cadence License Manager")

void show_err_text(LPTSTR lpszFunction, DWORD dwErrSn)
{
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf = NULL;
    LPVOID lpDisplayBuf = NULL;

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dwErrSn,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

    // Display the error message and exit the process

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
    StringCchPrintf((LPTSTR)lpDisplayBuf,
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%ls failed with error %d: %ls"),
        lpszFunction, dwErrSn, (LPTSTR)lpMsgBuf);

    printf("%ls\n", (LPCTSTR)lpDisplayBuf);

    LocalFree(lpMsgBuf);
    lpMsgBuf = NULL;

    LocalFree(lpDisplayBuf);
    lpDisplayBuf = NULL;
}

bool is_service_running(const TCHAR* psz_service_name)
{
    bool b_rc = false;

    SC_HANDLE schSCManager = NULL;
    SC_HANDLE schService = NULL;
    SERVICE_STATUS_PROCESS ssStatus;
    // DWORD dwOldCheckPoint;
    // DWORD dwStartTickCount;
    // DWORD dwWaitTime;
    DWORD dwBytesNeeded;

    DWORD dwErrSn = 0;

    do {


        // Get a handle to the SCM database. 

        // 需要管理员权限才行, 否则返回错误码5(拒绝访问)
        schSCManager = OpenSCManager(
            NULL,                    // local computer
            NULL,                    // servicesActive database 
            SC_MANAGER_ALL_ACCESS);  // full access rights 

        if (NULL == schSCManager)
        {
            dwErrSn = GetLastError();
            printf("OpenSCManager failed (%d)\n", dwErrSn);
            show_err_text((LPTSTR)TEXT("error"), dwErrSn);
            break;
        }

        // Get a handle to the service.

        schService = OpenService(
            schSCManager,         // SCM database 
            psz_service_name,            // name of service 
            SERVICE_ALL_ACCESS);  // full access 

        if (schService == NULL)
        {
            printf("OpenService failed (%d)\n", GetLastError());
            break;
        }

        // Check the status in case the service is not stopped. 

        if (!QueryServiceStatusEx(
            schService,                     // handle to service 
            SC_STATUS_PROCESS_INFO,         // information level
            (LPBYTE)&ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded))              // size needed if buffer is too small
        {
            printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
            break;
        }

        b_rc = (SERVICE_RUNNING == ssStatus.dwCurrentState);
    } while (false);

    if (NULL != schService)
    {
        CloseServiceHandle(schService);
        schService = NULL;
    }

    if (NULL != schSCManager)
    {
        CloseServiceHandle(schSCManager);
        schSCManager = NULL;
    }

    return b_rc;
}

bool try_to_run_service(const TCHAR* psz_service_name)
{
    bool b_rc = false;

    SC_HANDLE schSCManager = NULL;
    SC_HANDLE schService = NULL;
    SERVICE_STATUS_PROCESS ssStatus;
    // DWORD dwOldCheckPoint;
    // DWORD dwStartTickCount;
    // DWORD dwWaitTime;
    DWORD dwBytesNeeded;

    DWORD dwErrSn = 0;

    do {
        // Get a handle to the SCM database. 

        // 需要管理员权限才行, 否则返回错误码5(拒绝访问)
        schSCManager = OpenSCManager(
            NULL,                    // local computer
            NULL,                    // servicesActive database 
            SC_MANAGER_ALL_ACCESS);  // full access rights 

        if (NULL == schSCManager)
        {
            dwErrSn = GetLastError();
            printf("OpenSCManager failed (%d)\n", dwErrSn);
            show_err_text((LPTSTR)TEXT("error"), dwErrSn);
            break;
        }

        // Get a handle to the service.

        schService = OpenService(
            schSCManager,         // SCM database 
            psz_service_name,            // name of service 
            SERVICE_ALL_ACCESS);  // full access 

        if (schService == NULL)
        {
            printf("OpenService failed (%d)\n", GetLastError());
            break;
        }

        // Check the status in case the service is not stopped. 

        if (!QueryServiceStatusEx(
            schService,                     // handle to service 
            SC_STATUS_PROCESS_INFO,         // information level
            (LPBYTE)&ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded))              // size needed if buffer is too small
        {
            printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
            break;
        }

        b_rc = (SERVICE_RUNNING == ssStatus.dwCurrentState);
        if (b_rc)
        {
            break; // 服务当前状态就是running, 不用再启动服务了
        }

        // 尝试启动服务
        if (!StartService(
            schService,  // handle to service 
            0,           // number of arguments 
            NULL))      // no arguments 
        {
            dwErrSn = GetLastError();
            printf("StartService failed (%d)\n", dwErrSn);
            show_err_text((LPTSTR)TEXT("error"), dwErrSn);
            break;
        }

        printf("Service start pending...\n");
        b_rc = true;
    } while (false);

    if (NULL != schService)
    {
        CloseServiceHandle(schService);
        schService = NULL;
    }

    if (NULL != schSCManager)
    {
        CloseServiceHandle(schSCManager);
        schSCManager = NULL;
    }

    return b_rc;

}

int main()
{
    bool b_rc = false;
    int i_retry_cnt = 0;

    setlocale(LC_ALL, "chs"); // 控制台显示中文

    do {
        Sleep(1000 * 1); // 每转一圈, 都睡1秒, 然后再干活. 放置出现意外情况导致CPU占用率太高

        if (is_service_running(DST_SERVICE_NAME))
        {
            printf("service[%ls] was runing ...\n", DST_SERVICE_NAME);
            // break; // 如果服务已经启动了,退出程序

            // 即使检测到服务已经启动了,也不能退出
            // 发现即使成功启动服务,这个烂服务有可能还会停掉...
        }
        else {
            // 尝试启动服务
            b_rc = try_to_run_service(DST_SERVICE_NAME);
            printf("[%d] try to run service[%ls] %s\n", ++i_retry_cnt, DST_SERVICE_NAME, b_rc ? "success" : "failed");
        }

        // 睡1分钟, 再检测
        // 因为发现即使成功启动服务,这个烂服务有可能还会停掉...
        Sleep(1000 * 60);
    } while (true);
    return EXIT_SUCCESS;
}

编译好后,将exe拷贝出来,放到工程根目录,防止不小心被改掉或被删除。

Automation License Manager Service经常性自动停止 automation license manager启动_cadence

建立计划任务启动守护程序

打开控制面板

Automation License Manager Service经常性自动停止 automation license manager启动_守护程序_02


打开系统与安全

Automation License Manager Service经常性自动停止 automation license manager启动_服务_03


打开管理工具

Automation License Manager Service经常性自动停止 automation license manager启动_cadence_04


打开计划任务

Automation License Manager Service经常性自动停止 automation license manager启动_#include_05


创建计划任务

Automation License Manager Service经常性自动停止 automation license manager启动_cadence_06

任务的设置

Automation License Manager Service经常性自动停止 automation license manager启动_cadence_07


Automation License Manager Service经常性自动停止 automation license manager启动_SPB17.4_08


Automation License Manager Service经常性自动停止 automation license manager启动_服务_09


Automation License Manager Service经常性自动停止 automation license manager启动_服务_10


Automation License Manager Service经常性自动停止 automation license manager启动_服务_11


Automation License Manager Service经常性自动停止 automation license manager启动_服务_12

设置cadence服务为手动

如果cadence服务设置为自动,如果能每次都可靠启动服务,我也不用写守护程序由计划任务来启动了。

Automation License Manager Service经常性自动停止 automation license manager启动_服务_13


Automation License Manager Service经常性自动停止 automation license manager启动_守护程序_14

开机以后的验证

重启计算机后,已经实验了几次,都可以可靠将cadence服务启动起来。
看守护程序的UI日志,只启动了一次服务,以后每次检测,都是cadence服务是运行状态。

我很怀疑以前cadence服务不能可靠启动的原因是开机时,网络还没连上。

因为cadence服务依赖网络服务。

Automation License Manager Service经常性自动停止 automation license manager启动_守护程序_15

开机以后的效果

Automation License Manager Service经常性自动停止 automation license manager启动_cadence_16

总结

自己遇到啥问题后,先看看网上各位大佬有没有解决方案。
如果有,就试试,省时间。
如果没有(或者找到的方法不好使),就得自己想折了。
分析问题,猜测故障原因,整理解决问题的思路,实践验证,重复以上4步,直到完全搞定(凑合能用也行,自己能接受就是全部)😃

补充 - 2022_0408_1035

今天要装一个软件,需要断网才能装。
装完软件,需要重启。
计算机重启后,我特意看了一下,cadence服务的守护程序并没有启动,好久好久都没启动。想起来,我设置计划服务时,勾选了"只有任何网络连接时才启动"这个选项。

等我连接网络连接后,守护程序立刻启动起来了。
这挺好的,确保了没有网络连接不会启动cadence服务。有了网络连接后,立刻就可以启动cadence服务。这样cadence服务的启动就是100%成功的。