Я вызываю 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;
}
У меня тоже была такая же проблема, но при запросе 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
);
В отличие от других вызовов API WIN32, это не возвращает абсолютное количество необходимых байтов, но дополнительный байтов, необходимых относительно параметра cbBufSize. В качестве эксперимента я предоставил преднамеренно небольшой буфер и просто удваивал его каждый раз, чтобы выяснить, что система будет возвращать в ответ pcbBytesNeeded. В этой системе размер 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.
Поскольку служебная информация не является статичной, и теоретически все может произойти между двумя вызовами EnumServicesStatusEx()
этот API лучше всего использовать итеративно.
Как предполагает Кэт Марсен, вы можете, конечно, многократно пытаться получить все сервисы (зацикливание и увеличение буфера, пока API возвращает ERROR_MORE_DATA).
Но это имеет 2 недостатка:
Напротив, мы можем только предположить, что IMO, у автора, ответственного за EnumServicesStatusEx (), была причина спроектировать его следующим образом:
И точка возобновления, и оставшаяся часть обеспечивают гораздо более простой итеративный подход.
Позвольте мне продемонстрировать полные реальные функции, работающие достаточно хорошо и показывающие некоторые вкусности 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);
}