I kinda feel I should know this. How can I get the address of a virtual class method, given an instance? I want to pass it as a regular function pointer.

I kinda feel I should know this. How can I get the address of a virtual class method, given an instance? I want to pass it as a regular function pointer.

Any relatively nice ways without using an asm block?

Comments

  1. I don't know the answer to your question. I would like to say it is probably a bad design idea. Could you explain why you need such construction? What is the problem it solves for you?
    --
    http://francois-piette.blogspot.be

    ReplyDelete
  2. IIRC class methods have a reference to the class metadata as the first parameter, so they are not well suitable as regular callback functions.

    ReplyDelete
  3. François Piette Just playing a bit, trying to make a routine go faster. I'm passing a class to a function and calling a virtual class method in a loop, and Delphi doesn't store the class pointer in a register so becomes an unnecessary memory load per call. This is mostly just for fun :-)

    Eugene Mayevski I'll double check but I'm pretty certain I saw the compiler only push the parameters on stack. Since I will be the sole consumer of the function pointer it's not an issue though.

    ReplyDelete
  4. Asbjørn Heid You may just declare a data type for the method and assign it. Something like this:

    type
      TMyClass = class
        class function Demo(N : Integer) : Integer; virtual;
      end;
      TMyFct = function (N : Integer) : Integer of object;
      TForm1 = class(TForm)
        Memo1: TMemo;
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      end;
    var
      Form1: TForm1;
    implementation
    {$R *.dfm}
    { TMyClass }
    class function TMyClass.Demo(N: Integer): Integer;
    begin
        Result := N + N;
    end;
    { TForm1 }
    procedure TForm1.Button1Click(Sender: TObject);
    var
        F : TMyFct;
        I : Integer;
        R : Integer;
    begin
        F := TMyClass.Demo;
        R := 1;
        for I := 0 to 4 do
            R := F(R);
        Memo1.Lines.Add(IntToStr(R)); // Should display 32
    end;

    --
     http://francois-piette.blogspot.be

    ReplyDelete
  5. François Piette Hm, I'll try that thanks. Reason why I'm using virtual class methods is that I'm using generics, so quite limited by that. While better than nothing, Delphi generics are the worst of both worlds.

    ReplyDelete
  6. Asbjørn Heid The more we discuss, the more I understand that you have incompletely stated your real problem. If you want good help, please completely describe what you need. I mean the problem you have to solve, not the way you think you can solve it.

    ReplyDelete
  7. François Piette I'm using a generic base class to define some abstract class methods, which I use to constrain a type T in another generic function Foo. In Foo I call these class methods in a loop.

    As mentioned I was just curious if there was a way to directly call the class methods without the extra dereference.

    ReplyDelete
  8. This is of course to get around the limitations caused by the compiler instantiating the generic methods when it parses them, rather then on use.

    ReplyDelete
  9. Asbjørn Heid You're welcome to show your code.

    ReplyDelete
  10. Eugene Mayevski If you look at the code I pasted above, here's the disassembly:
    Project1.dpr.51: result := NumberImpl.Add(result, A[i]);
    0041C027  push dword ptr [esp]
    0041C02A  push dword ptr [ebx]
    0041C02C  mov eax,[$0041a220]
    0041C031  call dword ptr [eax+$04]
    0041C034  fstp dword ptr [esp]

     So does indeed just push the two parameters.

    ReplyDelete
  11. Asbjørn Heid
    Depends on Delphi version. In C++Builder 2007 there's a vmt parameter for class methods, while in C++Builder XE5 there's no such parameter declared.

    ReplyDelete
  12. Sounds like an attempt at premature micro-optimization... :). BTDT.
    While it can be fun (and frustrating) to try and find ways to get the compiler to generate better code, most of the time the results for real-world usage will be unmeasurable.

    ReplyDelete
  13. Well, it's mostly frustrating when you so often end up against a silly limitation or internal compiler errors. Fun when you don't :)

    ReplyDelete
  14. Eugene Mayevski Ah ok. I'm using XE3.

    ReplyDelete
  15. Hallvard Vassbotn Just doing a plain sum is of course just an example. While it's likely a micro-op like you say, unfortunately I can't test how much it would bring to the table, since I can't get any alternatives working. François Piette's method of declaring a method pointer generates an ICE.

    ReplyDelete
  16. The internal compiler error is due to Algorithms being declared as a record and not class. Once I changed it it compiles fine, but generates the wrong code. The method "adder" points to is Numbers.Add, not the concrete implementation, causing an abstract error. Here's what I'm using now:

    type
      BinaryOperator = function(const A, B: T): T of object;
    var
      adder: BinaryOperator;
    begin
      adder := NumberImpl.Add;
    ...

    Oh well, worth a shot.

    ReplyDelete
  17. Ok, finally got it. http://nopaste.dk/p64815

    Had to go via an instance variable.

    ReplyDelete
  18. For reference: doing Kahan summation using the function pointers is 2-3% faster than the "plain" approach when using either single or double data type. Kahan summation performs 4 operations per loop iteration.

    Not to mention the summation code looks a lot cleaner.

    ReplyDelete
  19. Try passing your doubles back & forth through var params, on an Inc() prototype ie. something like procedure(var A, B: T)

    The call overhead in your case isn't the issue, the stack juggling is.

    ReplyDelete
  20. Eric Grange I know it's not the primary factor by far, and yes passing by var is unfortunately much more effective than not. Too bad the optimizer sucks as it really makes the code hard to read.

    Anyway, I can now finally do something almost C++-like:
      sum := Algorithms.Sum(myArrayOfSingles);
      vsum := Algorithms.Sum(myArrayOfVector3);

    ReplyDelete
  21. Asbjørn Heid For simple primitive types like these you're better of using dedicated code IMHO.

    Not just for performance (which won't be C++ class), but also for clarity (the generics syntax looks terrible) and debug-ability: your call stacks will be hideous, stepping through them will be painful, and maintaining the code will be even worse.

    ReplyDelete
  22. Eric Grange The idea is was to support more than simple primitive types, and more specifically user-defined types. Anyway, just playing around :)

    ReplyDelete

Post a Comment