Is it safe to use a method's nested procedure as a winapi callback?
Asked Answered
C

2

11

This is the simplified scenario, in 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;

The whole thing seems to work fine, but is it really correct and safe? I'd like to have it encapsulated this way because it's more readable and clean. My doubt is whether the nested procedure is a simple, normal procedure or not, so that it can have its own calling convention (stdcall) and safely reference outer method's local variables (pRequest).

Thank you.

Clop answered 1/9, 2016 at 7:45 Comment(1)
I wouldn't even do it without using "outer" variables but accessing pRequest seems begging for errors. :-)Tendency
G
13

The implementation of local functions in 32 bit Delphi compilers for Windows means that such code does work as you intend. Provided that you don't refer to anything from the enclosing function. You can't refer to local variables including the Self reference. Your comment suggests that you wish to refer to pRequest, a local variable. You'll have to refrain from doing so for the reasons described above.

However, even when following these rules, it only works due to an implementation detail. It is explicitly stated as illegal in the documentation:

Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.

If ever you take your code to a different platform such as 64 bit Windows then it will fail. This issue is covered in more detail here: Why cannot take address to a nested local function in 64 bit Delphi?

My advice is that you do not use local functions in this way at all. Doing so just sets traps for yourself that you will fall into sometime in the future.

I would also advise using strongly typed declarations for the callback functions so that the compiler can check your callback has the correct signature. That requires redecoration of any Win32 API function because of Embarcadero's sloppy declarations using untyped pointers. You'll also want to give up using @ to obtain function pointers and so let the compiler work for you.

Grillroom answered 1/9, 2016 at 8:7 Comment(0)
S
6

The Method Pointers documentation advises against this practice:

Nested procedures and functions (routines declared within other routines) cannot be used as procedural values, nor can predefined procedures and functions.

The behaviour is undefined.

Stockstill answered 1/9, 2016 at 7:57 Comment(4)
Thank you very much. I accepted David's answer just because it's more complete, but I got the point. I read the documentation but didn't understand that "as procedural values" was my case.Clop
@yankee: afaik, it isn't. It takes David's answer to understand how the compiler affects the inline procedure to be usable as a callback (or unusable) but that's imo completely different from procedural values.Malia
No, that's what the documentation means, @Lieven. It uses the term procedural types to cover pointers to ordinary subroutines, method pointers, and method references. A value is an instantiation of a type, so a procedural value is an instantiation of a procedural type. That includes Yankee's case.Jive
@RobKennedy - somehow I never made that connection, most likely because here we are dealing with untyped pointers (did I at least got that right?). Thanks for the explanation.Malia

© 2022 - 2024 — McMap. All rights reserved.