为什么 GetServiceDisplayNameW() 和 GetServiceDisplayNameA() 返回不同的所需字符缓冲区大小?

问题描述 投票:0回答:2

这里是一个示例代码(只是示例代码以便于理解,没有错误处理,没有关闭句柄等等):

SC_HANDLE hSCManager = ::OpenSCManager(nullptr, nullptr, 0);

DWORD buffSize = 0;
::GetServiceDisplayName(hSCManager, m_serviceName, nullptr, &buffSize);

LPTSTR buff = new TCHAR[++buffSize];
VERIFY(::GetServiceDisplayName(hSCManager, m_serviceName, buff, &buffSize));

我的示例服务的显示名称为

"notepad starter"
(15 个字符)。

在构建配置之间切换,

GetServiceDisplayName()
在 ANSI (
GetServiceDisplayNameA
) 下返回缓冲区大小为 30,在 UNICODE (
GetServiceDisplayNameW
) 下返回 15。

此 API 的文档表示,它返回以 characters 为单位的缓冲区大小,不包括空终止符(没有详细记录,但我希望缓冲区大小在第二次调用中包含空终止符)。

为什么它在不同的构建配置中返回不同的缓冲区大小?

c++ winapi windows-services
2个回答
3
投票

首先

GetServiceDisplayName
将服务控制管理器数据库(hSCManager)的句柄作为第一个参数,但不处理服务(hService) - 因此您不需要为此任务打开服务。这里不需要
SC_MANAGER_ALL_ACCESS
,但 0 就足够了。

但是你的主要错误在接下来。您分配缓冲区

new TCHAR[buffSize + 1]
- 所以
buffSize + 1
为字符 - 这是正确的,因为
GetServiceDisplayName
返回服务显示名称的大小,不包括空终止字符 - 所以我们需要额外的一个字符空间来终止 0;

但在下一行错误 -

&buffSize
- 最后一个参数 lpcchBuffer 必须包含缓冲区的大小(以字符为单位)。正是您分配的缓冲区大小。但您分配的是
buffSize + 1
空间,而不是
buffSize
。所以代码必须是下一个:

if (SC_HANDLE hSCManager = OpenSCManagerW(nullptr, nullptr, 0))
{
    DWORD cch = 0;
    if (!GetServiceDisplayNameW(hSCManager, m_serviceName, nullptr, &cch))
    {
        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            PWSTR buff =(PWSTR)alloca(++cch*sizeof(WCHAR));
            if (GetServiceDisplayNameW(hSCManager, m_serviceName, buff, &cch))
            {
                DbgPrint("%S\n", buff);
            }
        }
    }
    CloseServiceHandle(hSCManager);
}

因此您必须在代码中将

buffSize + 1
替换为
++buffSize


关于 ansi 版本 -

GetServiceDisplayNameA
- 这里 api 实现中确实存在错误 - 如果字符缓冲区大小不够大 - 它返回需要多少 bytes 需要 unicode 服务名称(不包括空终止符号)。如果缓冲区足够大,则根本不会更新lpcchBuffer。这还有一个论点从不使用
A
版本的 api,但总是
W


2
投票

我认为正确的答案是在 6 个月后(我在 3 年后才看到的)来自 Raymond Chen;

为什么它报告的所需缓冲区大小比实际大小更大 真的需要吗?

因为字符集转换比较困难。

当您调用GetServiceDisplayNameA函数(ANSI版本)时, 它将调用转发给 GetServiceDisplayNameW 函数(Unicode 版本)。如果 Unicode 版本说:“抱歉,该缓冲区太 小的;它需要足够大以容纳 N 个 Unicode 字符,” ANSI 版本不知道翻译成多少个 ANSI 字符。 单个 Unicode 字符可以扩展到多达两个 ANSI ANSI 代码页为 DBCS 时的字符。这 GetServiceDisplayNameA 函数谨慎行事并采用 最坏的情况是服务显示名称完全由 需要两个 ANSI 字符来表示的 Unicode 字符。

这就是它过度报告缓冲区大小的原因。

devblogs.microsoft.com/oldnewthing

© www.soinside.com 2019 - 2024. All rights reserved.