英文:
COM function that return BSTR** does not work as it seems to me it should , but same function not from COM work like expected
问题
我正在尝试编写一个具有Windows服务管理功能的COM对象。为此,我想要一个返回所有服务名称的函数。我已经熟悉Windows Api几天了,所以我不太明白我在做什么(错误)以及如何更好地完成它。
在另一个程序中,我以以下方式调用这个函数:
```c
...
BSTR* pServiceNames = (BSTR*)CoTaskMemAlloc(sizeof(BSTR));;
DWORD dwServicesReturned = 0;
hr = pIService->GetServices(&pServiceNames, &dwServicesReturned);
...
然后我尝试类似这样:
std::wcout << (pServiceNames[0]); // 结果: AdobeARMservice
std::wcout << (pServiceNames[1]); // 结果: (进程 8844) 以代码 -1073741819 退出。
如果使用 "printf" 也是一样的。
std::cout << (pServiceNames[1]); // 结果: 000000084D454D4C
当我将相同的函数粘贴到我的主程序中时,一切正常,即所有服务名称都会显示出来。
附加信息:对于COM,我使用了ATL,也许这很重要。
<details>
<summary>英文:</summary>
I am trying to write a COM object with Windows Service Managment functionality. For this, I want a function that returns the names of all services. I've been familiar with Windows Api for a few days, so I don't really understand what I'm doing (wrong) and how I can do it better.
STDMETHODIMP CServiceHandler::GetServices(BSTR** pOut, LPDWORD dwServicesReturned)
{
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (!hSCManager)
{
return HRESULT_FROM_WIN32(GetLastError());
}
DWORD dwBytesNeeded = 0;
DWORD dwResumeHandle = 0;
EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, 0, &dwBytesNeeded, dwServicesReturned, &dwResumeHandle);
if (GetLastError() != ERROR_MORE_DATA)
{
CloseServiceHandle(hSCManager);
return HRESULT_FROM_WIN32(GetLastError());
}
LPENUM_SERVICE_STATUS lpServices = (LPENUM_SERVICE_STATUS)malloc(dwBytesNeeded);
if (!EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpServices, dwBytesNeeded, &dwBytesNeeded, dwServicesReturned, &dwResumeHandle))
{
free(lpServices);
CloseServiceHandle(hSCManager);
return HRESULT_FROM_WIN32(GetLastError());
}
BSTR* pServiceNames = (BSTR*)malloc(*dwServicesReturned * sizeof(BSTR));
if (!pServiceNames)
{
free(lpServices);
CloseServiceHandle(hSCManager);
return E_OUTOFMEMORY;
}
ZeroMemory(pServiceNames, *dwServicesReturned * sizeof(BSTR));
for (DWORD i = 0; i < *dwServicesReturned; i++)
{
pServiceNames[i] = SysAllocString(lpServices[i].lpServiceName);
}
*pOut = pServiceNames;
// //return S_OK;
free(lpServices);
CloseServiceHandle(hSCManager);
return S_OK;
In another program where I call this function this way:
...
BSTR* pServiceNames = (BSTR*)CoTaskMemAlloc(sizeof(BSTR));;
DWORD dwServicesReturned = 0;
hr = pIService->GetServices(&pServiceNames, &dwServicesReturned);
...
Then I try something like this:
`std::wcout << (pServiceNames[0]); // result: AdobeARMservice`
`std::wcout << (pServiceNames[1]); // result: (process 8844) exited with code -1073741819.`
Same if use "printf". And
`std::cout << (pServiceNames[1]); //result: 000000084D454D4C`
When I paste the same function into my main program, everything is fine, i.e. all the service names are displayed.
Additional information: For COM I used ATL, maybe it's important.
</details>
# 答案1
**得分**: 1
你对`EnumServiceStatus()`的处理不完整,因为你没有考虑`ERROR_INSUFFICIENT_BUFFER`。对`EnumServiceStatus()`的每次调用可能需要更多内存,因此可能报告`ERROR_INSUFFICIENT_BUFFER`,或者可能只返回部分数据,报告`ERROR_MORE_DATA`。你需要处理这两种情况,以及当报告`ERROR_MORE_DATA`时,可能已返回一些有效数据,需要进行处理。因此,在枚举过程中,可能需要多次重新分配你的`BSTR`数组。
此外,在COM中传递的所有内存都需要使用COM的内存管理器分配,但`GetServices()`正在使用`malloc()`分配输出的`BSTR`数组。你的调用代码在调用`GetServices()`之前本地调用了`CoTaskMemAlloc()`,然后`GetServices()`覆盖了调用者的指针,从而泄漏了调用者分配的内存。调用者不应该在本地分配任何内存,只需接管`GetServices()`输出的内存。
有了这些信息,你可以尝试像这样改进代码:
```cpp
STDMETHODIMP CServiceHandler::GetServices(BSTR** pServices, LPDWORD pdwServicesReturned)
{
if (!(pServices && pdwServicesReturned))
return E_POINTER;
*pServices = NULL;
*pdwServicesReturned = 0;
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!hSCManager)
return HRESULT_FROM_WIN32(GetLastError());
LPENUM_SERVICE_STATUS lpStatuses = NULL;
DWORD dwBytesAllocated = 0;
DWORD dwBytesNeeded = 0;
DWORD dwNumStatuses = 0;
DWORD dwResumeHandle = 0;
BSTR* pServiceNames = NULL;
DWORD dwNumServiceNames = 0;
BOOL bSuccess;
DWORD dwError = 0;
HRESULT hRes = S_OK;
do
{
bSuccess = EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpStatuses, dwBytesAllocated, &dwBytesNeeded, &dwNumStatuses, &dwResumeHandle);
if (!bSuccess)
{
dwError = GetLastError();
if ((dwError != ERROR_INSUFFICIENT_BUFFER) &&
(dwError != ERROR_MORE_DATA))
{
hRes = HRESULT_FROM_WIN32(dwError);
goto Failed;
}
}
if ((bSuccess || (dwError == ERROR_MORE_DATA)) &&
(dwNumStatuses > 0))
{
BSTR* pTmpServiceNames = (BSTR*)CoTaskMemRealloc(pServiceNames, sizeof(BSTR) * (dwNumServiceNames + dwNumStatuses));
if (!pTmpServiceNames)
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
pServiceNames = pTmpServiceNames;
for (DWORD i = 0; i < dwNumStatuses; ++i)
{
pServiceNames[dwNumServiceNames] = SysAllocString(lpStatuses[i].lpServiceName);
if (!pServiceNames[dwNumServiceNames])
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
++dwNumServiceNames;
}
}
if (!bSuccess)
{
LPENUM_SERVICE_STATUS lpTmpStatuses = (LPENUM_SERVICE_STATUS)realloc(lpStatuses, dwBytesNeeded);
if (!lpTmpStatuses)
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
lpStatuses = lpTmpStatuses;
}
} while (!bSuccess);
goto Finished;
Failed:
if (pServiceNames)
{
for (DWORD i = 0; i < dwNumServiceNames; ++i)
SysFreeString(pServiceNames[i]);
CoTaskMemFree(pServiceNames);
pServiceNames = NULL;
}
dwNumServiceNames = 0;
Finished:
free(lpStatuses);
CloseServiceHandle(hSCManager);
*pServices = pServiceNames;
*pdwServicesReturned = dwNumServiceNames;
return hRes;
}
BSTR* pServiceNames = NULL;
DWORD dwServicesReturned = 0;
hr = pIService->GetServices(&pServiceNames, &dwServicesReturned);
if (hr == S_OK)
{
...
for (DWORD i = 0; i < dwServicesReturned; ++i)
SysFreeString(pServiceNames[i]);
CoTaskMemFree(pServiceNames);
}
或者,既然你将问题标记为C++,你应该使用C++习惯来更有效地管理内存,例如:
struct SCHandle
{
SC_HANDLE m_SC;
SCHandle(SC_HANDLE hSC) : m_SC(hSC) {}
~SCHandle() { if (m_SC) CloseServiceHandle(m_SC); }
bool operator !() const { return !m_SC; }
operator SC_HANDLE() { return m_SC; }
};
STDMETHODIMP CServiceHandler::GetServices(BSTR** pServices, LPDWORD pdwServicesReturned)
{
if (!(pServices && pdwServicesReturned))
return E_POINTER;
try
{
*pServices = NULL;
*pdwServicesReturned = 0;
SCHandle hSCManager(OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
if (!hSCManager)
return HRESULT_FROM_WIN32(GetLastError());
std::vector<BYTE> statusBuffer;
std::vector<std::wstring> serviceNames;
LPENUM_SERVICE_STATUS lpStatuses = NULL;
DWORD dwBytesNeeded = 0;
DWORD dwNumStatuses = 0;
DWORD dwResumeHandle = 0;
DWORD dwError = 0;
do
{
BOOL bSuccess = EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpStatuses, statusBuffer.size(), &dwBytesNeeded, &dwNumStatuses, &dwResumeHandle);
if (!bSuccess)
{
dwError = GetLastError();
if ((dwError != ERROR_INSUFFICIENT_BUFFER) &&
(dwError != ERROR_MORE_DATA))
return HRESULT_FROM_WIN32(dwError);
}
if (bSuccess || (dwError == ERROR_MORE_DATA))
{
serviceNames.reserve(serviceNames.size() + dwNumStatuses);
for (DWORD i = 0; i < dwNumStatuses; ++i)
serviceNames.push_back(lpStatuses[i].lpServiceName);
}
if (!bSuccess)
{
statusBuffer.resize(dwBytesNeeded);
lpStatuses = reinterpret_cast<LPENUM_SERVICE_STATUS>(statusBuffer.data());
}
} while (!bSuccess);
DWORD dwNumServiceNames =
<details>
<summary>英文:</summary>
Your handling of `EnumServiceStatus()` is incomplete, as you are not taking `ERROR_INSUFFICIENT_BUFFER` into account. Any given call to `EnumServiceStatus()` may require more memory, thus reporting `ERROR_INSUFFICIENT_BUFFER`, or it may return only a subset of data, reporting `ERROR_MORE_DATA`. You need to handle both cases, as well as the fact that when `ERROR_MORE_DATA` is reported, some valid data may have been returned which you need to process. So, you may end up having to reallocate your `BSTR` array more than once during the course of your enumeration.
Also, all memory passed around in COM needs to be allocated with COM's memory manager, but `GetServices()` is allocating the output `BSTR` array using `malloc()`. Your calling code is calling `CoTaskMemAlloc()` locally before calling `GetServices()`, and then `GetServices()` is overwriting the caller's pointer, thus leaking the memory that the caller allocated. The caller should not be allocating any memory locally at all, only taking ownership of the memory that `GetServices()` outputs.
With that said, try something more like this:
STDMETHODIMP CServiceHandler::GetServices(BSTR** pServices, LPDWORD pdwServicesReturned)
{
if (!(pServices && pdwServicesReturned))
return E_POINTER;
*pServices = NULL;
*pdwServicesReturned = 0;
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!hSCManager)
return HRESULT_FROM_WIN32(GetLastError());
LPENUM_SERVICE_STATUS lpStatuses = NULL;
DWORD dwBytesAllocated = 0;
DWORD dwBytesNeeded = 0;
DWORD dwNumStatuses = 0;
DWORD dwResumeHandle = 0;
BSTR* pServiceNames = NULL;
DWORD dwNumServiceNames = 0;
BOOL bSuccess;
DWORD dwError = 0;
HRESULT hRes = S_OK;
do
{
bSuccess = EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpStatuses, dwBytesAllocated, &dwBytesNeeded, &dwNumStatuses, &dwResumeHandle);
if (!bSuccess)
{
dwError = GetLastError();
if ((dwError != ERROR_INSUFFICIENT_BUFFER) &&
(dwError != ERROR_MORE_DATA))
{
hRes = HRESULT_FROM_WIN32(dwError);
goto Failed;
}
}
if ((bSuccess || (dwError == ERROR_MORE_DATA)) &&
(dwNumStatuses > 0))
{
BSTR *pTmpServiceNames = (BSTR*) CoTaskMemRealloc(pServiceNames, sizeof(BSTR) * (dwNumServiceNames + dwNumStatuses));
if (!pTmpServiceNames)
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
pServiceNames = pTmpServiceNames;
for (DWORD i = 0; i < dwNumStatuses; ++i)
{
pServiceNames[dwNumServiceNames] = SysAllocString(lpStatuses[i].lpServiceName);
if (!pServiceNames[dwNumServiceNames])
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
++dwNumServiceNames;
}
}
if (!bSuccess)
{
LPENUM_SERVICE_STATUS lpTmpStatuses = (LPENUM_SERVICE_STATUS) realloc(lpStatuses, dwBytesNeeded);
if (!lpTmpStatuses)
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
lpStatuses = lpTmpStatuses;
}
}
while (!bSuccess);
goto Finished;
Failed:
if (pServiceNames)
{
for (DWORD i = 0; i < dwNumServiceNames; ++i)
SysFreeString(pServiceNames[i]);
CoTaskMemFree(pServiceNames);
pServiceNames = NULL;
}
dwNumServiceNames = 0;
Finished:
free(lpStatuses);
CloseServiceHandle(hSCManager);
*pServices = pServiceNames;
*pdwServicesReturned = dwNumServiceNames;
return hRes;
}
BSTR* pServiceNames = NULL;
DWORD dwServicesReturned = 0;
hr = pIService->GetServices(&pServiceNames, &dwServicesReturned);
if (hr == S_OK)
{
...
for(DWORD i = 0; i < dwServicesReturned; ++i)
SysFreeString(pServiceNames[i]);
CoTaskMemFree(pServiceNames);
}
Alternatively, since you did tag the question as C++, you should use C++ idioms to help manage memory more effectively, eg:
struct SCHandle
{
SC_HANDLE m_SC;
SCHandle(SC_HANDLE hSC) : m_SC(hSC) {}
~SCHandle() { if (m_SC) CloseServiceHandle(m_SC); }
bool operator !() const { return !m_SC; }
operator SC_HANDLE() { return m_SC; }
};
STDMETHODIMP CServiceHandler::GetServices(BSTR** pServices, LPDWORD pdwServicesReturned)
{
if (!(pServices && pdwServicesReturned))
return E_POINTER;
try
{
*pServices = NULL;
*pdwServicesReturned = 0;
SCHandle hSCManager(OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
if (!hSCManager)
return HRESULT_FROM_WIN32(GetLastError());
std::vector<BYTE> statusBuffer;
std::vector<std::wstring> serviceNames;
LPENUM_SERVICE_STATUS lpStatuses = NULL;
DWORD dwBytesNeeded = 0;
DWORD dwNumStatuses = 0;
DWORD dwResumeHandle = 0;
DWORD dwError = 0;
do
{
BOOL bSuccess = EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, lpStatuses, statusBuffer.size(), &dwBytesNeeded, &dwNumStatuses, &dwResumeHandle);
if (!bSuccess)
{
dwError = GetLastError();
if ((dwError != ERROR_INSUFFICIENT_BUFFER) &&
(dwError != ERROR_MORE_DATA))
return HRESULT_FROM_WIN32(dwError);
}
if (bSuccess || (dwError == ERROR_MORE_DATA))
{
serviceNames.reserve(serviceNames.size() + dwNumStatuses);
for (DWORD i = 0; i < dwNumStatuses; ++i)
serviceNames.push_back(lpStatuses[i].lpServiceName);
}
if (!bSuccess)
{
statusBuffer.resize(dwBytesNeeded);
lpStatuses = reinterpret_cast<LPENUM_SERVICE_STATUS>(statusBuffer.data());
}
}
while (!bSuccess);
DWORD dwNumServiceNames = serviceNames.size();
BSTR *pServiceNames = static_cast<BSTR*>(CoTaskMemAlloc(sizeof(BSTR) * dwNumServiceNames));
if (!pServiceNames)
return E_OUTOFMEMORY;
for (DWORD i = 0; i < dwNumServiceNames; ++i)
{
pServiceNames[i] = SysAllocString(serviceNames[i].c_str());
if (!pServiceNames[i])
{
for(DWORD j = 0; j < i; ++j)
SysFreeString(pServiceNames[j]);
CoTaskMemFree(pServiceNames);
return E_OUTOFMEMORY;
}
}
*pServices = pServiceNames;
*pdwServicesReturned = dwNumServiceNames;
return S_OK;
}
catch (const std::bad_alloc &)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_UNEXPECTED;
}
}
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论