1

我在我的代码中调用 EnumServicesStatusEx() 两次,第一次应该失败并将正确的缓冲区大小放在 dwBuffNeeded 中,这样当我第二次调用它时,缓冲区大小应该是正确的。但是,有时,在第二次通话后,我并不总是得到 ERROR_MORE_DATA 。任何想法为什么?谢谢

DWORD pId=GetCurrentProcessId();
    SC_HANDLE hSCM    = NULL;
    PUCHAR  pBuf    = NULL;
    ULONG  dwBufSize   = 0x00;
    ULONG  dwBufNeed   = 0x00;
    ULONG  dwNumberOfService = 0x00;
    LPENUM_SERVICE_STATUS_PROCESS pInfo = NULL;

    hSCM = OpenSCManager( NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT );

    if (hSCM == NULL)
    {
        GetCustomLog().Log( SV_ERROR, 10004807, "Could not open Service Control Manager: %s", GetLastOSErrorString().c_str() );
        return;
    }

    //Query services once to get correct buffer size, always fails
    if ( EnumServicesStatusEx(
        hSCM,
        SC_ENUM_PROCESS_INFO,
        SERVICE_WIN32, 
        SERVICE_ACTIVE,
        NULL,
        dwBufSize,
        &dwBufNeed,
        &dwNumberOfService,
        NULL,
        NULL) == 0 )
    {

        DWORD err = GetLastError();
        if ( ERROR_MORE_DATA == err )
        {
            dwBufSize = dwBufNeed + 0x10;
            pBuf  = (PUCHAR) malloc(dwBufSize);

            //Query services again with correct buffer size
            if ( EnumServicesStatusEx(
                hSCM,
                SC_ENUM_PROCESS_INFO,
                SERVICE_WIN32, 
                SERVICE_ACTIVE,
                pBuf,
                dwBufSize,
                &dwBufNeed,
                &dwNumberOfService,
                NULL,
                NULL ) == 0 )

            {
//FAILS HERE
                GetCustomLog().Log( SV_ERROR, 10004808, "Could not enumerate services, error: %s", GetLastOSErrorString().c_str() );
                free(pBuf);
                return;
            }
        }
        else
        {
            GetCustomLog().Log( SV_ERROR, 10004809, "Could not enumerate services, error: %s", GetLastOSErrorString().c_str() );
            return;
        }
4

2 回答 2

2

我也遇到了同样的问题,但是在查询 SERVICE_STATE_ALL 时,我认为每次调用都需要相同数量的内存(除非已安装/卸载服务)。仅使用 pcbBytesNeeded 参数中返回的大小的缓冲区重试是不够的:

BOOL WINAPI EnumServicesStatusEx(
  _In_         SC_HANDLE hSCManager,
  _In_         SC_ENUM_TYPE InfoLevel,
  _In_         DWORD dwServiceType,
  _In_         DWORD dwServiceState,
  _Out_opt_    LPBYTE lpServices,
  _In_         DWORD cbBufSize,
  _Out_        LPDWORD pcbBytesNeeded,
  _Out_        LPDWORD lpServicesReturned,
  _Inout_opt_  LPDWORD lpResumeHandle,
  _In_opt_     LPCTSTR pszGroupName
);

与其他 WIN32 API 调用不同,这不是返回所需的绝对字节数,而是相对于 cbBufSize 参数所需的额外字节数。作为一个实验,我故意提供了一个小缓冲区,并且每次都将其翻倍,以找出系统将在 pcbBytesNeeded 中返回什么作为响应。在这个系统上 sizeof(ENUM_SERVICE_STATUS_PROCESS) 是 56 字节。最后一行是导致成功调用的最小缓冲区。

+-----------+----------------+
| cbBufSize | pcbBytesNeeded | 
+-----------+----------------+
|      112  |         37158  |
|      224  |         37013  |
|      448  |         36766  |
|      896  |         36374  |
|     1792  |         35280  |
|     3584  |         33202  |
|     7168  |         28972  |
|    14336  |         20765  |
|    28672  |          4215  |
|    32032  |             0  |
+-----------+----------------+

您可以看到每一行粗略地加起来所需的缓冲区大小,而且就系统而言,所需的缓冲区大小也不是很可预测。本例调用成功返回的服务条目数为233,只需要13048字节。事实证明,由 ENUM_SERVICE_STATUS_PROCESS lpDisplayName 和 lpServiceName 指针指向的字符串只是存储在提供的缓冲区的尾部(我一直想知道它们的支持在哪里,为什么它是稳定的并且不需要分开释放)。无论如何,这解释了一些不确定的响应以及奇数:系统可能具有不适合的当前条目并且确切地知道它需要多少大小,但猜测其余部分。

下面的代码是可靠的,通常只需要两个调用,但有时可能需要三个(即使使用 SERVICE_STATE_ALL)。我不知道为什么需要三个,系统的低估一次会持续几分钟,但最终会自行解决。我从来没有见过它需要四个电话。

int EnumerateAllServices(SC_HANDLE hSCM) {
  void* buf = NULL;
  DWORD bufSize = 0;
  DWORD moreBytesNeeded, serviceCount;
  for (;;) {
    printf("Calling EnumServiceStatusEx with bufferSize %d\n", bufSize);
    if (EnumServicesStatusEx(
        hSCM,
        SC_ENUM_PROCESS_INFO,
        SERVICE_WIN32,
        SERVICE_STATE_ALL,
        (LPBYTE)buf,
        bufSize,
        &moreBytesNeeded,
        &serviceCount,
        NULL,
        NULL)) {
      ENUM_SERVICE_STATUS_PROCESS* services = (ENUM_SERVICE_STATUS_PROCESS*)buf;
      for (DWORD i = 0; i < serviceCount; ++i) {
        printf("%s\n", services[i].lpServiceName);
      }
      free(buf);
      return 0;
    }
    int err = GetLastError();
    if (ERROR_MORE_DATA != err) {
      free(buf);
      return err;
    }
    bufSize += moreBytesNeeded;
    free(buf);
    buf = malloc(bufSize);
  }
}

“+=”是“技巧”,并没有很清楚地记录(但是,事后看来,我现在理解了 MSDN 对 pcbBytesNeeded 参数的解释中的细微差别):

pcbBytesNeeded [out]

    A pointer to a variable that receives the number of bytes
    needed to return the remaining service entries, if the buffer
    is too small.
于 2013-09-23T15:42:25.333 回答
1

因为服务信息不是静态的,理论上任何事情都可能发生在两次调用之间EnumServicesStatusEx(),所以最好迭代地使用这个 API。

正如 Kat Marsen 建议的那样,您当然可以反复尝试获取所有服务(只要 API 返回 ERROR_MORE_DATA,就循环和增长缓冲区)。
但这有两个缺点:

  1. 你告诉 SCM 不断填写它实际上已经发回给你的信息(即使它相当罕见,我实际上想避免它)。
  2. 根据 MSDN 文档,缓冲区的硬上限为 256K 字节。正如我在上面的评论中提到的,后者实际上不太可能;作为程序员,您仍然想安全行事。

迭代方法

相反,我们只能假设 IMO 负责 EnumServicesStatusEx() 的作者有理由这样设计它:

  • 一个缓冲区,如果有效且足够大,至少可用于一项服务,则立即填充。
  • SCM 为尚未迭代的服务假定的剩余字节的输出参数。
  • 最重要的是,有一个输出参数告诉您恢复点。

恢复点和剩余点都允许更容易的迭代方法。

让我展示完整的真实世界功能,工作得相当好,并展示一些 C++ 好东西。
chunk_fetch_all_win32_services预先分配一些内存,为每个块调用一个回调,并让回调决定它是消耗内存还是离开它以便重用。
enum_all_win32_services消耗每个块,然后为每个单独的服务调用回调。

#include <type_traits>
#include <memory>
#include <stddef.h>
#include <Windows.h>

using std::unique_ptr;
using std::error_code;
using std::system_error;
using std::system_category;
using std::function;
using std::make_unique;


/** @short Fetch all win32 services in chunks.

    This function fetches all services of type `SERVICE_WIN32=(SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS)`,
    in chunks as decided by the SCM (refer to EnumServicesStatusEx()).

    @param scmHandle Handle to the SCM with access right SC_MANAGER_ENUMERATE_SERVICE
    @param stateMask One of SERVICE_ACTIVE, SERVICE_INACTIVE, SERVICE_STATE_ALL
    In the callback you can decide whether you want to consume the passed memory or leave
    it in order to be reused.

    @note This is most probably rare but expect the callback being invoked multiple times
    in case the SCM didn't return information about all services at once.
 */
bool chunk_fetch_all_win32_services(SC_HANDLE scmHandle, DWORD stateMask, const function<bool(unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]>&, DWORD /*nServices*/)>& cb, error_code* ec)
{
    // (optionally) preallocate
    // (the amount stems from Win XP's upper size of 64k;
    //  on a typical Win7 system there are about 220 services (about 34k))
    unique_ptr<BYTE[]> mem = make_unique<BYTE[]>(64 * 1024);
    DWORD nAllocated = 64 * 1024, nRemaining;
    DWORD resumePoint = 0;
    do
    {
        DWORD nServices;
        if (!EnumServicesStatusEx(scmHandle, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, stateMask, mem.get(), nAllocated, &nRemaining, &nServices, &resumePoint, nullptr))
        {
            const int errorCode = GetLastError();
            if (errorCode != ERROR_MORE_DATA)
            {
                if (!ec)
                    throw system_error{ errorCode, system_category(), "Can't enumerate services" };
                ec->assign(errorCode, system_category());
                return false;
            }
        }

        if (nServices)
        {
            // memory initialized, transfer ownership to typed pointer
            unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]> cache{ LPENUM_SERVICE_STATUS_PROCESS(mem.release()) };
            if (!cb(cache, nServices))
                // early bail-out requested
                return true;

            // document that the typed pointer can be 'downgraded' again without the need to destroy objects
            static_assert(std::is_trivially_destructible_v<ENUM_SERVICE_STATUS_PROCESS>);
            // possibly reuse existing buffer
            mem.reset(PBYTE(cache.release()));
        }

        if (nRemaining)
        {
            // re-allocate if buffer too small or consumed by callback
            if (!mem || nAllocated < nRemaining)
                mem = make_unique<BYTE[]>(nRemaining),
                nAllocated = nRemaining;
        }

        // loop as long as there are more services to be enumerated
    } while (nRemaining);

    return true;
}

/** @short Enumerate all win32 services.

    This function enumerates all services of type `SERVICE_WIN32=(SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS)`.

    @param scmHandle Handle to the SCM with access right SC_MANAGER_ENUMERATE_SERVICE
    @param stateMask One of SERVICE_ACTIVE, SERVICE_INACTIVE, SERVICE_STATE_ALL
    @param cb Callback receiving process information of a single service process information and the number of services.

    @note This is most probably rare but expect the number of services passed to the callback to change
    in case the SCM didn't return information about all services at once; if, then this of course happens
    after the provided number of information items have been iterated.
 */
bool enum_all_win32_services(SC_HANDLE scmHandle, DWORD stateMask, const function<bool(ENUM_SERVICE_STATUS_PROCESS&, DWORD /*nServices*/)>& cb, error_code* ec)
{
    return chunk_fetch_all_win32_services(scmHandle, stateMask, [&cb](unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]>& cache, DWORD nServices)
    {
        for (size_t idx = 0; idx < nServices; ++idx)
        {
            if (!cb(cache[idx], nServices))
                // early bail-out requested
                return true;
        }

        return true;
    }, ec);
}
于 2018-09-22T19:24:24.563 回答