【问题标题】:How Do I Use NotifyServiceStatusChange to Get Notified When a Service Is Deleted?删除服务时如何使用 NotifyServiceStatusChange 获取通知?
【发布时间】:2022-08-14 05:36:53
【问题描述】:

如何正确使用 NotifyServiceStatusChange,以便在指定的服务被删除时收到通知?我当前的代码成功停止了服务并将其标记为删除。但是,我希望在服务完全删除时收到通知。

以下是我的代码的要点:

SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, 
     SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);

HANDLE EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);

SERVICE_NOTIFY ServiceNotify;
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pszServiceNames = ServiceName;
ServiceNotify.pContext = &EventHandle;
ServiceNotify.pfnNotifyCallback = (PFN_SC_NOTIFY_CALLBACK)CallbackFunction;
DWORD status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);

WaitForSingleObject(EventHandle, INFINITE);
CloseServiceHandle(SCManager);
CloseHandle(EventHandle);

(服务名称为WCHAR*

回调函数代码:

VOID CALLBACK CallbackFunction(IN PVOID pParameter) {
    SERVICE_NOTIFY* ServiceNotify = pParameter;
    HANDLE EventHandle = *(HANDLE*)ServiceNotify->pContext;
    SetEvent(EventHandle);
}

NotifyServiceStatusChange 正在返回 ERROR_SUCCESS (0)。但是,我的回调函数根本没有被调用。我怎样才能解决这个问题?

编辑: 这是最小的可重现代码:

void ErrorExit(char* FunctionName, unsigned long ErrorCode) {
    char* ErrorMessage;
    FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ErrorCode, LANG_USER_DEFAULT, (LPTSTR)&ErrorMessage, 0, NULL);

    int MessageSize = (strlen(ErrorMessage) + strlen(FunctionName) + 50) * sizeof(char);
    char* FullMessage = malloc(MessageSize);
    sprintf_s(FullMessage, MessageSize, \"%s failed with error %d: %s\", FunctionName, ErrorCode, ErrorMessage);

    MessageBoxA(NULL, FullMessage, \"Error\", MB_OK);
    ExitProcess(ErrorCode);
}

PFN_SC_NOTIFY_CALLBACK CallbackFunction(PVOID pParameter) {
    printf(\"CallbackFunction has been called.\\r\\n\");
    SERVICE_NOTIFY* ServiceNotify = pParameter;
    HANDLE EventHandle = ServiceNotify->pContext;
    if (!SetEvent(EventHandle)) {
        ErrorExit(\"SetEvent\", GetLastError());
    }
}

int main()
{
    WCHAR* ServiceName = L\"SERVICE NAME\"; // Input service name here

    SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
    if (!SCManager) { 
        ErrorExit(\"OpenSCManagerW\", GetLastError()); 
    }

    SC_HANDLE ServiceHandle = OpenServiceW(SCManager, ServiceName,
        SERVICE_ENUMERATE_DEPENDENTS | SERVICE_STOP | DELETE);
    if (!ServiceHandle) {
        ErrorExit(\"ServiceHandle\", GetLastError());
    }

    if (!DeleteService(ServiceHandle)) {
        ErrorExit(\"DeleteService\", GetLastError());
    }

    if (!CloseServiceHandle(ServiceHandle)) {
        ErrorExit(\"CloseServiceHandle\", GetLastError());
    }

    HANDLE EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
    if (!EventHandle) { 
        ErrorExit(\"CreateEventW\", GetLastError()); 
    }

    SERVICE_NOTIFY ServiceNotify;
    ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
    ServiceNotify.pszServiceNames = ServiceName;
    ServiceNotify.pContext = EventHandle;
    ServiceNotify.pfnNotifyCallback = CallbackFunction;

    DWORD status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
    if (status != ERROR_SUCCESS) { 
        ErrorExit(\"NotifyServiceStatusChangeW\", GetLastError()); 
    }

    status = WaitForSingleObjectEx(EventHandle, INFINITE, TRUE);
    if (status == WAIT_FAILED) {
        ErrorExit(\"WaitForSingleObjectEx\", GetLastError());
    }
    printf(\"WaitForSingleObjectEx Result: %lu\\r\\n\", status);
    system(\"pause\");
    return 0;
}

当我运行它时,没有其他服务依赖于被删除的服务,并且被删除的服务已经停止。我的错误处理函数 \"ErrorExit\" 从未被调用过。屏幕上没有打印任何内容。我的程序只是暂停,我假设它来自 WaitForSingleObjectEx。

我知道该服务正在被删除,因为我打开了 ProcessHacker,它会通知我该服务正在被删除。

  • 与您的问题无关,只是仅供参考,在将EventHandle 分配给pContext 时,您不需要使用& 运算符,因为HANDLE 已经是一个指针类型。
  • 您的回调函数被声明为错误的。 PFN_SC_NOTIFY_CALLBACK CallbackFunction(PVOID pParameter) 应该改为 VOID CALLBACK CallbackFunction(PVOID pParameter)。然后您可以在将CallbackFunction 分配给ServiceNotify.pfnNotifyCallback 时摆脱类型转换
  • 此外,WCHAR* ServiceName = L\"SERVICE NAME\"; 不会在任何符合标准的编译器中编译。您需要改用const WCHAR*(又名LPCWSTR)。
  • @RemyLebeau 谢谢。这些建议有助于使其发挥作用。我认为主要是它不是const WCHAR*。您可以创建一个新答案或编辑您的原始答案以便我接受吗?
  • 我不需要更新任何内容,我的答案中的示例已经符合我上面提到的所有要点。

标签: c++ c windows winapi


【解决方案1】:

NotifyServiceStatusChange 正在返回 ERROR_SUCCESS (0)。但是,我的回调函数根本没有被调用。

NotifyServiceStatusChangeW documentation 说:

当服务状态发生变化时,系统调用指定的回调函数作为排队到调用线程的异步过程调用(APC)。调用线程必须进入警报等待(例如,通过调用SleepEx 函数)才能接收通知。有关详细信息,请参阅Asynchronous Procedure Calls

因此,请确保您在等待时实际上正在处理 APC 通知。 WaitForSingleObject() 不会为您这样做。

请改用WaitForSingleObjectEx()。它有一个bAlertable 参数,您可以将其设置为TRUE。你将不得不在一个循环中调用它,因为它会在什么时候返回任何APC 调用由调用线程处理,这可能不是您所期望的。

您还需要循环调用NotifyServiceStatusChangeW()。文档没有提到这一点,但每次使用只会调用回调 1 次。调用回调后,如果当前不是您期望的事件,您需要再次调用NotifyServiceStatusChangeW() 以接收另一个通知。

话虽如此,尝试更像这样的东西:

struct MyCallbackInfo {
    HANDLE EventHandle;
    LPCWSTR pszServiceName;
    bool bDeleted;
};

...

VOID CALLBACK CallbackFunction(PVOID pParameter) {
    SERVICE_NOTIFYW* ServiceNotify = (SERVICE_NOTIFYW*) pParameter;
    MyCallbackInfo *ci = (MyCallbackInfo*) ServiceNotify->pContext;

    if (ServiceNotify->dwNotificationStatus == ERROR_SUCCESS) {
        LPWSTR pServiceName = ServiceNotify->pszServiceNames;
        while (*pServiceName != L'\0') {
            if (lstrcmpW(pServiceName, ci->pszServiceName) == 0) {
                ci.bDeleted = true;
                break;
            }
            pServiceName += (lstrlenW(pServiceName) + 1);
        }
        LocalFree(ServiceNotify->pszServiceNames);
    }

    SetEvent(ci->EventHandle);
}

...

MyCallbackInfo ci;
ci.EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
ci.pszServiceName = ServiceName;
ci.bDeleted = false;

if (!ci.EventHandle) {
    // error handling...
}

SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, 
     SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!SCManager) {
    // error handling...
}

SERVICE_NOTIFYW ServiceNotify = {};
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pContext = &ci;
ServiceNotify.pfnNotifyCallback = CallbackFunction;

DWORD status;
do {
    status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
    if (status != ERROR_SUCCESS) {
        // error handling...
    }

    while ((status = WaitForSingleObjectEx(ci.EventHandle, INFINITE, TRUE)) == WAIT_IO_COMPLETION);

    if (status == WAIT_FAILED) {
        // error handling...
    }

    if (ci.bDeleted) {
        // service has been deleted ...
        break;
    }

    ResetEvent(ci.EventHandle);
}
while (true);

CloseServiceHandle(SCManager);
CloseHandle(ci.EventHandle);

【讨论】:

  • 这个答案可能有助于防止将来发生问题。但是,我的回调函数仍然没有被调用。
  • 我的回调函数仍未被调用。 SERVICE_NOTIFY 的注释说回调函数定义为typedef VOID( CALLBACK * PFN_SC_NOTIFY_CALLBACK ) ( IN PVOID pParameter );。但是,我不知道如何应用它。
  • @Hantalyte 我回答中的回调满足该签名。如果回调没有被调用,那么服务实际上并没有被删除(是否有打开的句柄?检查SysInternals Process Explorer),或者您正在做其他我们看不到的错误。请edit您的问题提供minimal reproducible example
  • 我添加了最少的可重现代码。
  • @Hantalyte 您在调用NotifyServiceStatusChangeW() 之前调用了DeleteService()CloseServiceHandle(),因此完全有可能在您注册SERVICE_NOTIFY_DELETED 通知之前该服务已被完全删除,因此没有事件可以发送给您。先做注册。
猜你喜欢
  • 2015-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多