【问题标题】:Why so many pointers?为什么这么多指针?
【发布时间】:2017-06-30 18:22:45
【问题描述】:

搜索Win32 LDAP API函数的引用,找到了以下JwaWinLDAP.pas单元。

在这个单元上,函数ldap_search_st被声明:

function ldap_search_st(ld: PLDAP; base: PAnsiChar; scope: ULONG;
  filter, attrs: PAnsiChar; attrsonly:  ULONG; var timeout: TLDAPTimeVal;
  var res: PLDAPMessage): ULONG; cdecl;

超时时间:TLDAPTimeVal参数声明为:

  PLDAPTimeVal = ^TLDAPTimeVal;
  l_timeval = packed record
    tv_sec: Longint;
    tv_usec: Longint;
  end;
  LDAP_TIMEVAL = l_timeval;
  PLDAP_TIMEVAL = ^LDAP_TIMEVAL;
  TLDAPTimeVal = l_timeval;

在代码上,如果我使用类似的东西:

procedure foo;
var
  TimeVal: PLDAPTimeVal;
begin
 ldap_search_st(foo1, Nil, 0, PAnsiChar('(objectClass=*)'), Nil, 0, TimeVal, foo2);
end;

编译器给我错误:

[dcc32 错误] 实际和形式 var 参数的类型必须是 一样的

因为超时参数。如果我将 TimeVal 类型更改为 TLDAPTimeVal 它会编译并且应用程序可以工作。

问题是: 当我在 Delphi 中看到类型声明时,它们总是这样:

type
 PType1 = ^Type1
 Type1 = record...

在引用的具体示例中,可能是:

  l_timeval = packed record
    tv_sec: Longint;
    tv_usec: Longint;
  end;
  TLDAPTimeVal = l_timeval;

它会以完全相同的方式工作(我认为)......为什么对这种声明如此混乱?

【问题讨论】:

    标签: delphi


    【解决方案1】:
    type
     PType1 = ^Type1
     Type1 = record...
    

    上面的类型声明声明了两种类型——一种是记录,另一种是特定记录类型的类型指针。它们通常成对声明,因为它们是相关的。但是,如果您或任何代码不需要类型化指针,则不需要声明指针类型。


    函数ldap_search_st 仅使用声明的记录类型。但是该单元中的一些其他函数期望指针作为参数。这就是为什么声明两者兼而有之。

    有问题的代码是 Delphi 的 LDAP Windows API 标头转换。 Windows API 使用指针将结构传递给函数。

    API 翻译通常很复杂,有时似乎有多余的声明。为了完整起见,翻译通常包含所有原始声明(符号)——它们是l_timevalLDAP_TIMEVALPLDAP_TIMEVAL,虽然这些足以使用 API,但还有两个额外的声明,其唯一目的是提供 Delphi 风格为用户更友好的体验命名PLDAPTimeValTLDAPTimeVal

    如果您查看原始 LDAP 函数声明,它们都使用指针来传递结构。例如:

    ULONG ldap_search_st(
      _In_  LDAP             *ld,
      _In_  PCHAR            base,
      _In_  ULONG            scope,
      _In_  PCHAR            filter,
      _In_  PCHAR            attrs[],
      _In_  ULONG            attrsonly,
      _In_  struct l_timeval *timeout,
      _Out_ LDAPMessage      **res
    ); 
    

    ULONG ldap_connect(
      _In_ LDAP         *ld,
      _In_ LDAP_TIMEVAL *timeout
    );
    

    考虑到timeout 参数,这两者有一个区别。

    ldap_search_st 期望 timeout 参数中的非空值 - 并且该参数的 Delphi 转换为 var timeout: TLDAPTimeVal 以更清楚地匹配该意图 - 该声明可防止您意外传递空值。虽然TLDAPTimeVal 不是指针类型,但拥有var 关键字会使我们的timeout 参数表现得像一个。在幕后 Delphi 将传递指向结构的指针,这将与原始函数声明完美匹配。

    另一方面,ldap_connect timeout 可以包含空值。在这种情况下,将使用默认超时值。满足该要求的唯一方法是使用指针类型来超时结构。换句话说,PLDAPTimeVal 和该函数声明的 Delphi 翻译是

    function ldap_connect(ld: PLDAP; timeout: PLDAPTimeval): ULONG;
    

    【讨论】:

    • 我明白为什么在这个声明中它如此混乱,因为其他函数需要“混乱”......类型化指针的部分,我真的还是不明白什么时候使用指针等等......但这是另一个问题,我应该在文档上阅读更多关于它的信息......谢谢你的回答:)
    • 该代码与 Windows API 接口(这些函数参数由 API 定义),其中指针用于传递结构。所以你不能避免指针。
    • 请阅读:Addressing pointers。 Windows API 与 C 兼容,因此它们倾向于大量使用指针。有时,这些指针会被转换为 Delphi 接口单元中的var 参数。因此,Delphi 声明并不总是与原始 MSDN 文档中针对此类 API 函数的指针完全匹配。请参阅上篇文章中关于参考参数的部分。
    • @RudyVelthuis 附加阅读Pitfalls of converting
    • @Dalija:嗯,我已经读过了。但 userXYZ 或许应该这样做。
    【解决方案2】:

    这是编码标准和约定的问题,因为 LDAP 声明被采用到 PSDK 中,然后翻译到 Delphi,所以它们有很多。此外,由于 Pascal 不允许在形式参数中声明指针类型(例如 ^Integer),这与普通 C(例如 int *)不同,因此在声明中添加了相应的指针类型。

    这里,我在声明中标注了各种约定,注意大小写和前缀的区别:

      PLDAPTimeVal = ^TLDAPTimeVal;   // Delphi pointer (Econos convention)
      l_timeval = packed record       // canonic structure (LDAP convention)
        tv_sec: Longint;
        tv_usec: Longint;
      end;
      LDAP_TIMEVAL = l_timeval;       // Windows structure (PSDK convention)
      PLDAP_TIMEVAL = ^LDAP_TIMEVAL;  // Windows pointer (PSDK convention)
      TLDAPTimeVal = l_timeval;       // Delphi structure (Econos convention)
    

    奇怪的是:Delphi 指针的前向声明(before 结构)也是Econos convention 强制要求的。原始 PSDK 代码在结构之后声明了指针。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-16
      • 2021-10-13
      • 1970-01-01
      • 2016-05-15
      • 2012-07-21
      • 1970-01-01
      • 2016-05-23
      • 1970-01-01
      相关资源
      最近更新 更多