3

If you like to use a method's pointer as an argument, you need to type the method as function of object like this works good:

type TAcceptor = function(filename:string):boolean of object;
function acceptor(filename:string):boolean; 
begin 
  result := filename <> ''; 
end;

What if you like to use the pointer of a sub-method? It does not work:

procedure TForm1.Button1Click(Sender:TObject);
  function acceptor(filename:string):boolean of object;
  begin 
    result := filename <> ''; 
  end;
begin
end;

The error occour: ; expected but OF found!

Question: Is there any subfunction-pointer? Can i cast it?

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
Grim
  • 1,938
  • 10
  • 56
  • 123
  • The code in your question does not compile. You cannot write of object in a function definition. – David Heffernan Jun 01 '14 at 07:43
  • @DavidHeffernan you are right, i updated the code. – Grim Jun 01 '14 at 08:24
  • 1
    Sure the error occurs because the syntax is wrong; either the code samples have no relation to the question asked or I don't understand what is actually asked. – kludg Jun 01 '14 at 09:15
  • See [Why cannot take address to a nested local function in 64 bit Delphi?](http://stackoverflow.com/q/10162749/576719). – LU RD Jun 01 '14 at 20:55
  • @user246408 David did not meant the 2.snipplet, he ment the 1.snipplet, the first snipplet where i wrote `works good` was invalid. Check the edit-history to know why the 1.snipplet did not work. David did understand good that the 2.snipplet is only used to know what i try to do and he understand that the 2.snipplet never claims to be compilable. – Grim Jun 02 '14 at 11:05

2 Answers2

7

I don't see how that this would be possible.

http://docwiki.embarcadero.com/RADStudio/XE6/en/Procedural_Types

If you look under the method pointers section, it specifically says that nested procedures and functions cannot be used:

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

You might be able to work around it using an anonymous method. Something like:

procedure TForm1.Button1Click(Sender:TObject);
begin
  DoSomethingWithAcceptor(function(FileName: string): Boolean
  begin 
    Result := FileName <> '';
  end);
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
Graymatter
  • 6,529
  • 2
  • 30
  • 50
  • 4
    +1 This is spot on. By chance a nested function under x86, that does not use containing params/vars, can have its address taken successfully. But it is a mistake to do so as the docs say. Chickens come home to roost when that code is compiled under x64. – David Heffernan Jun 01 '14 at 07:45
  • Im restricted to Delphi7 for compatibility, too bad. Anyway your answer looks like the correct one. I guess i will take the first line as it is. – Grim Jun 01 '14 at 08:17
  • 1
    There is a way to mimic this (using nested procs). I have done it before, and it was done in Turbo Vision as a kind of "poor man's anonymous method". I'll try to post my code, if I can find it. – Rudy Velthuis Jun 01 '14 at 09:38
  • @Rudy Any such use of nested function relies on implementation detail and is not robust to compiler changing that detail, as has been discussed here so many times before. – David Heffernan Jun 01 '14 at 16:34
  • The implementation details for Win32 have not changed since Delphi 2, so for Delphi 7, there should be a ready solution. AFAIK, this solution works up to Delphi XE6, so for a whole range of versions, it should work. Of course, it won't work for Win64. It might probably even work for OS X, I never tried. – Rudy Velthuis Jun 01 '14 at 17:42
  • FWIW, I moved that code off my main system to a backup disk, and Windows is currently checking the (huge) backup disk, so it might take some time before I find it. – Rudy Velthuis Jun 01 '14 at 17:46
  • @rudy It's surprising that you recommend coding to implementation detail that is subject to change. – David Heffernan Jun 01 '14 at 19:40
  • 1
    As I said, it hasn't changed since Delphi 2, so it has been constant over many versions. And the question is about Delphi 7, which is one particular version. Is this detail subject to change? – Rudy Velthuis Jun 02 '14 at 06:31
  • @RudyVelthuis No, the Question is not bound to Delphi7. I am bound to Delphi7 for compatibility. – Grim Jun 02 '14 at 11:18
  • OK, but the procedure I mean, although it relies on undocumented internals, works for all Win32 versions that have been produced. If you know it is a hack (if I must code a hack, I clearly document it), you can always check if it still works in the next iteration of Delphi. If this must work in D7 too, there is no way you could use the better alternative, anonymous methods. So then you may benefit from my TLocal, as I called it. It is not here, so I'll post it this evening. – Rudy Velthuis Jun 02 '14 at 13:10
1

CAUTION

I know that the following is not universally applicable, but it works for all known Win32 versions of Delphi. As long as you are aware of this, and check its functionality in new versions, it is a viable hack, IMO.


Passing nested functions to methods

In older code, I used this to do some "poor man's anonymous methods":

type
  TLocal = packed record
    Code: Pointer;  // local (nested) function
    Frame: Pointer; // outer stack frame for local function
   end;

To fill such a local inside a method, I wrote the function Local:

function Local(LocalFunction: Pointer): TLocal;
asm
  MOV [EDX].TLocal.Frame,EBP
  MOV [EDX].TLocal.Code,EAX
end;

Inside my unit (some kind of generic collection), I wrote a function to call them, passing one parameter (of type TGeneric, in this case, which is not important here, you can also pass a pointer or some such).

// Calls local function using local closure provided, passing
// T as parameter to the local.
function CallLocal(T: TGeneric; const Local: TLocal): TGeneric;
asm 
  PUSH [EDX].TLocal.Frame
  CALL [EDX].TLocal.Code
  ADD ESP,4
end;

It was used like this:

function TStdCollection.AsArray: TGenericArray;
var
  I: Integer;
  A: TGenericArray;

  procedure ToArray(E: TGeneric);
  begin
    Result[I] := E.Traits.Copy(E);
    Inc(I);
  end;

begin
  SetLength(A, Count);
  I := 0;
  ForEach(Local(@ToArray));
  Assert(I = Count);
  Result := A;
end;

The code in the nested function makes a copy of the element and stores it in the array. The main procedure then passes the nested function ToArray (together with its stack frame) as parameter to ForEach, which is implemented this way:

function TStdCollection.ForEach(Operation: TLocal): ICollection;
var
  Enum: IEnumerator;
  Elem: TGeneric;
begin
  Enum := GetEnumerator;
  Elem := Enum.First;
  while Elem <> nil do
  begin
    CallLocal(Elem, Operation);
    Elem := Enum.Next;
  end;
  Result := Self;
end;

These examples show how to use the Locals. I hope this more or less answers your question.

Note

Note that this code was written in the Delphi 6 timeframe. I know there are better alternatives these days, like generics and anonymous methods. But if compatibility with Delphi 7 is required, the above might be a solution.

Community
  • 1
  • 1
Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94