【问题标题】:Working with WinAPI functions which use C style strings as OUT parameters使用使用 C 风格字符串作为 OUT 参数的 WinAPI 函数
【发布时间】:2013-02-05 19:56:09
【问题描述】:

给定一个 WinAPI 函数,它通过 C 风格的字符串 OUT 参数返回结果,例如:

int WINAPI GetWindowTextW(
   _In_   HWND hWnd,
   _Out_  LPTSTR lpString,
   _In_   int nMaxCount
);

有没有比我在下面做的更好的方法来使用这个函数?

HWND handle; // Assume this is initialised to contain a real window handle
std::wstring title;
wchar_t buffer[512];
GetWindowTextW(handle, buffer, sizeof(buffer));
title = buffer;

上面的代码有效,但我有以下问题:

  1. 缓冲区大小完全是任意的,因为我无法知道函数可能返回的字符串的长度。这对我来说“感觉”是错误的——我一直试图在我的代码中避免使用幻数。

  2. 如果函数返回的字符串大于缓冲区,它将被截断 - 这很糟糕!

  3. 每当函数返回一个小于缓冲区的字符串时,我都会浪费内存。这并不像(2)那么糟糕,但我对为实际上可能只需要几个字节的东西留出大块内存(例如上面示例中的 1024 字节)的想法并不感到兴奋。

还有其他选择吗?

【问题讨论】:

  • 天哪……你是真正的乔恩·宾利吗?
  • 您应该使用_countof,而不是sizeof - 所需的参数是最大字符数,而不是字节数。否则,没有,没有比你使用它更好的方法了——尽管“浪费”几百字节的堆栈并不是什么大问题,如果你想回收立即堆叠。
  • 您可以使用GetWindowTextLength() 来确定字符串的长度并分配适当大小的缓冲区。
  • @Pete 谢谢,如果您将其作为答案发布,我会投赞成票,因为它对于我给出的具体示例来说是一个很好的例子,如果不是一般问题的话。
  • 为什么不std::wstring title(buffer)

标签: c++ winapi cstring out


【解决方案1】:

使用不同大小的临时缓冲区多次调用该函数。从 8 的缓冲区开始。将缓冲区大小加倍并再次调用它。重复直到它返回与上次相同的计数。然后您可以分配确切大小的缓冲区并复制您在那里的内容。有许多具有类似行为的 Win32 函数。

您可以使用GetWindowTextLength(),但如果存在竞争条件(您最终可能会因此而截断文本),它可能不会有太大帮助。

【讨论】:

  • 有趣的 hack,谢谢。如果我找不到其他 WinAPI 函数的 GetWindowTextLength() 等效项(根据 Pete 在 cmets 中对我的问题的建议),我会考虑这一点。
  • Alexey 可以继续并为此选择复选标记...但我要指出,通常有很多方法可以与这种类型的许多 Windows API 函数进行类似的操作。许多返回数据中的字节数或字符数,您可以先传入一个空缓冲区以获取实际长度,然后使用它来创建缓冲区。我认为非常笨拙,但这就是适合您的 WinAPI。
【解决方案2】:

根据您使用的 Windows API 函数,有几种不同的模式。对于一些,您可以先查询,有时通过调用另一个函数(例如,GetWindowTextLengthW),但通常通过为缓冲区传递 NULL。查询后,分配大小,再次调用获取实际的字符串数据。

即使使用 query-allocate-query,您有时也需要迭代,因为可能存在竞争条件。例如,考虑如果窗口标题在 GetWindowTextLengthW 和 GetWindowTextW 调用之间发生变化会发生什么。

您还可以通过使用字符串本身而不是第二个缓冲区来避免额外的副本。

std::wstring GetWindowTitle(HWND hwnd) {
    std::wstring title(16, L'X');
    int cch;
    do {
      title.resize(2 * title.size());
      cch = GetWindowTextW(hwnd, &title[0], title.size());
    } while (cch + 1 == title.size());
    title.resize(cch);
    return title;
}

虽然很尴尬,但这并不是 Windows API 设计的错误。 API 被设计为 C 接口,而不是 C++。由于 C 不是面向对象的,因此在处理字符串方面非常有限。对于 C++ 代码,您可以像我在本示例中所做的那样包装这种簿记。

【讨论】:

  • 您的 GetWindowTitle() 示例有几个错误。如果GetWindowTextLength() 返回0,代码将无限循环。如果它返回>0,代码将永远不会循环超过1次迭代,因为cch永远不会是>= title.size()GetWindowText()返回复制的字符数不包括空终止符,但是第二个参数指定可以复制的最大字符包括空终止符,所以cch将始终为 < title.size() 并截断结果,除非两个函数都返回 0。我更正了它。
  • @RemyLebeau:谢谢。我在准备答案的过程中被打断,并在准备好之前无意中提交了。当我有机会时,我会再试一次,因为它仍然不完美。
  • 您没有正确分配初始wstring,没有对GetWindowText() 进行错误处理,也没有正确考虑空终止符。传递给GetWindowText() 的大小包括空终止符的空间,但返回值不包括空终止符。 size() 不包括位于 wstring 内存块末尾的空终止符。
  • @RemyLebeau:您的两个编辑都引入了缓冲区溢出。我的上一个版本用一个空标题、一个短标题和一个很长的标题进行了测试,并在所有情况下都返回了一个大小正确的字符串,没有缓冲区溢出。
  • @Remy 感谢你们两位提供的所有有用的 cmets。如果您能澄清一下切换到 std::vector 的含义,以及这将如何缓解您描述的问题,我将不胜感激?
猜你喜欢
  • 1970-01-01
  • 2011-04-17
  • 2011-12-11
  • 1970-01-01
  • 2016-08-17
  • 2017-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多