使用方法的嵌套过程作为winapi回调是安全的吗?

11

这是在Delphi 7中简化的场景:

procedure TMyClass.InternalGetData;
var
  pRequest: HINTERNET;

    /// nested Callback 
    procedure HTTPOpenRequestCallback(hInet: HINTERNET; Context: PDWORD; Status: DWORD; pInformation: Pointer; InfoLength: DWORD); stdcall;
    begin
      // [...]  make something with pRequest
    end;

begin
  pRequest := HTTPOpenRequest(...);
  // [...]   
  if (InternetSetStatusCallback(pRequest, @HTTPOpenRequestCallback) = PFNInternetStatusCallback(INTERNET_INVALID_STATUS_CALLBACK)) then
    raise Exception.Create('InternetSetStatusCallback failed');
  // [...]
end;
整个过程似乎运作良好,但这是真的正确和安全吗? 我喜欢用这种方式封装它,因为它更易读、更干净。我的疑问是嵌套的过程是否是一个简单的、正常的过程,以便它可以有自己的调用约定(stdcall)并安全地引用外部方法的局部变量(pRequest)。
谢谢。

我甚至不会在没有使用“外部”变量的情况下这样做,但访问pRequest似乎很容易出错。 :-) - Uli Gerhardt
2个回答

13
在32位Delphi编译器中实现本地函数的意思是代码可以按照你想要的方式工作,只要你不引用封闭函数中的任何内容,包括局部变量和Self。你的评论表明你想引用pRequest这个局部变量,但基于上述原因,你必须避免这样做。
然而,即使遵循这些规则,它也仅仅因为一个实现细节才能够运行。它在文档中明确声明为非法:
"嵌套过程和函数(在其他过程中声明的例程)不能用作过程值。"
如果您将代码移植到其他平台,例如64位Windows,则会失败。有关此问题的更多详细信息,请参见为什么无法在64位Delphi中获取嵌套本地函数的地址? 我的建议是完全不要以这种方式使用本地函数。这样做只会为自己设置陷阱,将来你会掉进去。
我还建议对回调函数使用强类型声明,以便编译器可以检查您的回调是否具有正确的签名。这需要重新定义任何Win32 API函数,因为Embarcadero使用无类型指针进行松散声明。您还需要放弃使用@来获取函数指针,让编译器为您工作。

6
方法指针文档不建议这样做:

嵌套的过程和函数(在其他过程中声明的过程)不能用作过程值,预定义的过程和函数也是如此。

行为未定义。

非常感谢。我接受了David的答案,因为它更完整,但我明白了。我阅读了文档,但没有理解"作为过程值"就是我的情况。 - yankee
@yankee: 就我所知,它不是。需要理解David的答案,才能明白编译器如何影响内联过程以便用作回调*(或不可用)*,但在我看来,这与过程值完全不同。 - Lieven Keersmaekers
3
不,@Lieven,这就是文档的意思。文档使用术语“过程类型”来涵盖普通子例程、方法指针和方法引用的指针。值是类型的实例化,因此过程值是过程类型的实例化。这包括Yankee的情况。 - Rob Kennedy
@RobKennedy - 不知怎么的,我从来没有想到过这一点,很可能是因为我们在处理未经类型定义的指针 *(我至少理解对了吧?)*。感谢您的解释。 - Lieven Keersmaekers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接