1

コードで EnumServicesStatusEx() を 2 回呼び出します。1 回目は失敗し、dwBuffNeeded に正しいバッファー サイズを入れて、2 回目に呼び出したときにバッファー サイズが正しくなるようにします。しかし、2 回目の呼び出しの後に 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 に何を返すかを調べるために毎回それを 2 倍にしました。このシステムでは、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 ポインターが指す文字列は、提供されたバッファーの末尾に格納されているだけであることがわかります (私は常に、これらのバッキングがどこにあるのか、なぜそれが安定しており、個別に存在する必要がないのか疑問に思っていました)解放された)。とにかく、これは奇数と同様にやや非決定論的な応答を説明しています。システムは現在のエントリが適合しない可能性があり、それに必要なサイズを正確に知っていますが、残りについて推測します。

以下のコードは信頼性が高く、通常は正確に 2 回の呼び出しが必要ですが、場合によっては 3 回 (SERVICE_STATE_ALL を使用した場合でも) かかることがあります。なぜ 3 つが必要なのか、システムによる過小評価が一度に数分間続くのかわかりませんが、最終的には解決します。4回の呼び出しが必要なのを見たことがありません。

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);
  }
}

「+ =」は「トリック」であり、あまり明確に文書化されていません(しかし、後から考えると、pcbBytesNeededパラメーターのMSDNの説明のニュアンスを理解しています):

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 に答える