【问题标题】:How do I pass nil to a var parameter?如何将 nil 传递给 var 参数?
【发布时间】:2015-11-22 21:17:12
【问题描述】:

有相当多的 API 例程将指向某个变量的指针作为参数转换为 var 参数,但根据 Windows 帮助文件可以将其指定为 nil 指针。

例如,ChangeDisplaySettings 函数声明为:

function ChangeDisplaySettings(var lpDevMode: TDeviceMode; dwFlags: DWORD): Longint; stdcall;

但 Windows 帮助文件明确指出“为 lpDevMode 参数传递 NULL 是在动态模式更改后返回默认模式的最简单方法。”正确的翻译应该是:

function ChangeDisplaySettings(lpDevMode: PDeviceMode; dwFlags: DWORD): Longint; stdcall;

我发布这个问题和答案是为了帮助新手解决这些问题,而无需重新声明函数。我仍然记得一开始这对我来说是个问题。

【问题讨论】:

  • 重新声明函数才是正确的解决方案
  • @David,谢谢;我尊重您作为德尔福经验丰富的老手的意见。不过,就个人而言,我不喜欢到处重新声明标准例程,而是尽可能地使用标准例程。但是,我将更新我的答案以表明标准做法似乎更喜欢重新声明。
  • 应该有两个重载。一个带有 var,一个带有指向结构的指针。然后调用者可以选择。

标签: delphi delphi-7


【解决方案1】:

一种解决方案是使用指针代替 var 参数重新声明任何此类函数,但有一个更简单的解决方案。只需将取消引用的 nil 指针转换为正确的类型,例如,对于 ChangeDisplaySettings 示例,使用以下命令将显示模式重置为默认注册表设置:

ChangeDisplaySettings(TDeviceMode(nil^), 0);

ChangeDisplaySettings(PDeviceMode(nil)^, 0);

通过这种方式,您传递的 var 参数恰好位于内存地址为零 - 编译器很高兴,您可以将 nil 指针传递给 API 例程!

编辑: 从 David Hefferman 的评论看来,标准做法似乎是重新声明此类例程。就个人而言,如果可以的话,我更喜欢在自己的个人单位中使用标准声明,但出于专业工作目的,我建议开发人员遵循标准做法。

【讨论】:

  • 就我个人而言,我发现您的第二个选项更自然。
  • @Andreas,我也更喜欢使用第二个,但在未声明指针类型的情况下列出了第一个。另一方面,David Hefferman 对重新声明的偏好是在新版本的 Delphi 修复标准声明时不会导致错误的唯一解决方案!
  • @Jannie - 这个解决方案会破坏的另一种方式是,如果编译器被更改以正确地将这种类型转换技巧识别为破坏参数合同并将代码拒绝为无效。在这种情况下提供的声明仅作为副作用是正确的。不要介意“标准做法”,正确的解决方案是使用正确的声明,而不是人为添加副作用来抵消另一个副作用。
  • @Deltics,我同意正确的解决方案是正确的声明,但我严重怀疑编译器是否会按照您的描述进行更改。将内存地址类型转换为指向某种类型的指针然后取消引用它对于 var 参数非常有效。在这种情况下,我们只是使用 nil(地址 0)。当然 nil 在 Delphi 中确实有特殊含义,但我仍然怀疑他们会开始禁止某些值,只是为了防止程序员以非标准方式使用该语言。
  • 我想补充一点,这种方法不适用于“out”参数,它们可能在函数开始执行时被隐式初始化。
【解决方案2】:

除了其他有用的答案和 cmets 之外,我对此还有另一个看法。在这种情况下,无论是谁从标头中翻译了这个 API,都没有真正仔细查看 API 文档。如果他们这样做了,很明显传递“nil”是一个有效的做法。

在这种情况下,正确的做法是声明一组引用相同导入的重载。一个是漂亮的“var”参数版本,另一个是“指向结构的指针”版本。这样您就可以直接传入 TDeviceMode 变量(无需获取 var 的地址)并在必要时仍然传入 nil。编译器会将“nil”“匹配”到指向结构的指针,然后引用该结构。由于两个 API 都解析为相同的 API,并且传递参数的实际方式没有区别,因此一切仍按预期工作。

由于没有该 API 的重载版本,因此您应该能够传递作为 API 翻译错误的“nil”。请随时在http://quality.embarcadero.com 的报告中指出这一点。

为了记录,这些年来我在产品中做了很多 API 翻译......当然可以想象 是没有进行适当研究的愚蠢开发人员在这个:)。

【讨论】:

  • 个人认为“不错的 var 参数版本”已经过时了。好的,自 Borland Pascal 时代以来一直在实践,但实际上,我更喜欢更接近原著的翻译,即总是使用指针。这样做的好处是,当在 Delphi 中使用“var”时,API 的文档(几乎总是基于 C)不会引起任何混淆(最小惊讶原则)。文档显示一个指针,翻译有一个指针。没有混淆,无需如此仔细地阅读文档。只有已经存在的翻译才需要重载。
  • 另外,不能为尚未标记为重载的函数声明重载,因此您必须修改原始导入单元,这并不总是可能的,或者您 隐藏原始的,并在你自己的单元中声明两个重载函数,其中一个是精确的重新声明。
  • 这就是为什么我建议在质量门户中报告它。我从来没有建议可以在没有将原始标记为超载的情况下完成它。
  • 好的。我实际上是想说服你和 Embarcadero 放弃“尽可能使用 var 而不是指针类型”的原则。
  • @Allen,不幸的是我目前只有 Delphi 7;所以我无法检查它是否仍然不正确。另一个是 IShellFolder.ParseDisplayName,如果不需要,dwAttributes 可以为 nil。不过,这并不严重;只需提供一个虚拟变量。翻译错误不会妨碍任何功能。
猜你喜欢
  • 2018-08-20
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 2021-11-08
  • 2023-03-25
  • 2013-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多