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.

yankee
  • 123
  • 5

2 Answers2

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.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
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.

fantaghirocco
  • 4,761
  • 6
  • 38
  • 48
  • 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. – yankee Sep 01 '16 at 08:43
  • @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. – Lieven Keersmaekers Sep 01 '16 at 10:40
  • 3
    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. – Rob Kennedy Sep 01 '16 at 12:25
  • @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. – Lieven Keersmaekers Sep 01 '16 at 13:41