为什么某些 WinAPI 函数需要将结构的大小作为参数传递?

Why do some WinAPI functions need sizes of structs passed as their parameters?

例如,考虑 SendInput。签名看起来像这样:

UINT WINAPI SendInput(
  _In_ UINT    nInputs,
  _In_ LPINPUT pInputs,
  _In_ int     cbSize
);

文档说:

cbSize [in] Type: int The size, in bytes, of an INPUT structure. If cbSize is not the size of an INPUT structure, the function fails.

既然函数已经使用了 INPUT 结构(并且可能对它的各个字段做了一些事情),难道它不应该事先知道结构的大小吗?

我能想到的唯一原因是,这是一种奇怪的向后兼容性技巧,可以使旧库二进制文件与可能在结构末尾引入新字段的新头文件兼容。

这是结构版本控制的一种简单形式。

更高版本的 API 可以在结构末尾添加更多字段,这将改变其大小。为旧版本编写的程序不会在较新的字段中设置值,cbSize 参数将反映这一点。 API 可以检查 cbSize 并知道它真正拥有哪个版本的结构,并在必要时为新字段提供默认值。

另一种方法是定义一个与旧结构有很多共同点的新结构,然后创建一个与旧结构非常相似的新 API。这是大量的代码重复,这使得旧程序更难使用更新的 SDK 重新编译并继续工作。

使用大小字段消除了对一堆重复代码的需要。这是用 C 语言做事的常用方法,但类型安全性较差。

但这也有点危险。如果调用者没有正确设置大小字段或者 API 实现不是很小心,这个方案可能会导致访问冲突、读取未初始化的字段或写入结构的末尾。

是的,这样做是为了让 Microsoft 可以在未来版本的 winapi 中更改 INPUT 结构。从传递的 cbSize 可以看出程序使用的是旧版本还是新版本的结构。这不是很容易从结构本身发现的。

将它作为参数传递给函数并不常见,它们通常将 cbSize 作为结构的成员嵌入。例如,与 WNDCLASSEX、MENUITEMINFO、SCROLLINFO、MSGBOXPARAMS 进行比较。对于像 SendInput() 和 GetMouseMovePointsEx() 这样的函数,使用单个参数更容易做到这一点,因为它们采用结构数组。

LVITEM, LVCOLUMN, etcetera, they don't use a cbSize at all. Using their api requires specifying the desired version explicitly in a manifest.

这样的结构采用另一种方法