COM function that return BSTR** does not work as it seems to me it should , but same function not from COM work like expected

huangapple go评论54阅读模式
英文:

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&#39;ve been familiar with Windows Api for a few days, so I don&#39;t really understand what I&#39;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, &amp;dwBytesNeeded, dwServicesReturned, &amp;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, &amp;dwBytesNeeded, dwServicesReturned, &amp;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 &lt; *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 &lt;&lt; (pServiceNames[0]);  // result: AdobeARMservice`

`std::wcout &lt;&lt; (pServiceNames[1]);  // result: (process 8844) exited with code -1073741819.`

Same if use &quot;printf&quot;. And
`std::cout &lt;&lt; (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&#39;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&#39;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&#39;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, &amp;dwBytesNeeded, &amp;dwNumStatuses, &amp;dwResumeHandle);
if (!bSuccess)
{	
dwError = GetLastError();
if ((dwError != ERROR_INSUFFICIENT_BUFFER) &amp;&amp;
(dwError != ERROR_MORE_DATA))
{
hRes = HRESULT_FROM_WIN32(dwError);
goto Failed;
}
}
if ((bSuccess || (dwError == ERROR_MORE_DATA)) &amp;&amp;
(dwNumStatuses &gt; 0))
{
BSTR *pTmpServiceNames = (BSTR*) CoTaskMemRealloc(pServiceNames, sizeof(BSTR) * (dwNumServiceNames + dwNumStatuses));
if (!pTmpServiceNames)
{
hRes = E_OUTOFMEMORY;
goto Failed;
}
pServiceNames = pTmpServiceNames;
for (DWORD i = 0; i &lt; 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&lt;BYTE&gt; statusBuffer;
std::vector&lt;std::wstring&gt; 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(), &amp;dwBytesNeeded, &amp;dwNumStatuses, &amp;dwResumeHandle);
if (!bSuccess)
{	
dwError = GetLastError();
if ((dwError != ERROR_INSUFFICIENT_BUFFER) &amp;&amp;
(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 &lt; dwNumStatuses; ++i)
serviceNames.push_back(lpStatuses[i].lpServiceName);
}
if (!bSuccess)
{
statusBuffer.resize(dwBytesNeeded);
lpStatuses = reinterpret_cast&lt;LPENUM_SERVICE_STATUS&gt;(statusBuffer.data());
}
}
while (!bSuccess);
DWORD dwNumServiceNames = serviceNames.size();
BSTR *pServiceNames = static_cast&lt;BSTR*&gt;(CoTaskMemAlloc(sizeof(BSTR) * dwNumServiceNames));
if (!pServiceNames)
return E_OUTOFMEMORY;
for (DWORD i = 0; i &lt; dwNumServiceNames; ++i)
{
pServiceNames[i] = SysAllocString(serviceNames[i].c_str());
if (!pServiceNames[i])
{
for(DWORD j = 0; j &lt; i; ++j)
SysFreeString(pServiceNames[j]);
CoTaskMemFree(pServiceNames);
return E_OUTOFMEMORY;
}
}
*pServices = pServiceNames;
*pdwServicesReturned = dwNumServiceNames;
return S_OK;
}
catch (const std::bad_alloc &amp;)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_UNEXPECTED;
}

}


</details>

huangapple
  • 本文由 发表于 2023年2月23日 21:44:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/75545659.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定