Results 1 to 5 of 5

Thread: Pointer to functions in a class

  1. #1

    Pointer to functions in a class

    Hi guys. Is there a way to obtain a pointer to a function that is inside a class?

    For example:

    //////////////////////////////////////////////////////////////

    type
    Tteste = class(TControl)
    private
    protected
    public
    function myFunction(var1: Integer): Boolean;
    function OtherFunction: Boolean;
    published
    end;

    function Tteste.OtherFunction: Boolean;
    var p: Pointer;
    begin
    p:= @myFunction; // Here
    end;

    function Tteste.myFunction(var1: Integer): boolean;
    begin

    end;


    //////////////////////////////////////////////////////////////

    This code doesn't work. But If I declare myFunction as a global Function (not Class function), it works.

    Is there a way to do this ?

    Thank you.

  2. #2

    Pointer to functions in a class

    When you say it doesn't work, do you mean it doesn't compile, or what?
    [size=10px][ Join us in #pgd on irc.freenode.net ] [ Sign the Petition for a Software Patent Free Europe ][/size]

  3. #3

    Pointer to functions in a class

    It doesn't compile.

    What I'm trying to do is to make a CALLBACK function to use with RegisterClassEx (win32api).

    I would like to pass to RegisterClassEx a callback function that is inside my class. But i can't get it working!

    Do you have any idea?

  4. #4

    Pointer to functions in a class

    You can't pass a method as a callback function, because methods have a hidden parameter pointing to the object, i.e,
    [pascal]procedure TYourClass.YourMethod(Stuff: Integer);[/pascal]
    is really:
    [pascal]procedure TYourClass.YourMethod(Self: TYourClass; Stuff: Integer);[/pascal]
    So, for callback functions, you need to use a normal (global) function.

    I don't know about the RegisterClassEx function, however, though I'm sure Alimonster or someone will be able to enlighten you.
    [size=10px][ Join us in #pgd on irc.freenode.net ] [ Sign the Petition for a Software Patent Free Europe ][/size]

  5. #5

    Pointer to functions in a class

    As Useless Hacker said, you have to watch out for the hidden parameter ("self") when dealing with methods of an object.

    I've not yet figured out the way to associate a wndproc properly -- it gets called fine but the CreateWindowEx part also seems to return 0 instead of a valid handle :?.

    Remember that the WndProc won't be associated with individual class instances, but will be common to the entire class. I've got a standard timer proc working fine from within a class, but not the window proc. I guess it's to do with one being a procedure vs a function. Anyway, here's a quick example of how to go about this sort of thing. I've used SetTimer here and passed it along to a class' class method to be dealt with. SetTimer, of course, wants a callback in a similar way to the WndProc:

    [pascal][background=#FFFFFF][comment=#0000FF][normal=#000000][number=#C00000][reserved=#000000][string=#00C000]unit static_main;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls;

    type
    TStaticThing = class
    private
    protected
    public
    class procedure TheTimerProc(hwnd: HWND; uMsg, idEvent: UINT;
    dwTime: DWORD); stdcall;
    end;

    //================================================== ============================

    TfrmCallbackMain = class(TForm)
    btnStart: TButton;
    btnStop: TButton;
    procedure FormKeyDown(Sender: TObject; var Key: Word;
    Shift: TShiftState);
    procedure btnStartClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    private
    { Private declarations }
    FStaticTest: TStaticThing;
    public
    { Public declarations }
    end;

    var
    frmCallbackMain: TfrmCallbackMain;

    implementation

    {$R *.dfm}

    //------------------------------------------------------------------------------

    class procedure TStaticThing.TheTimerProc(hwnd: HWND; uMsg, idEvent: UINT;
    dwTime: DWORD); stdcall;
    begin
    ShowMessage('Interesting');
    end;

    //------------------------------------------------------------------------------

    procedure TfrmCallbackMain.FormKeyDown(Sender: TObject; var Key: Word;
    Shift: TShiftState);
    begin
    if Key = VK_ESCAPE then
    Close;
    end;

    //------------------------------------------------------------------------------

    procedure TfrmCallbackMain.btnStartClick(Sender: TObject);
    var
    Where: pointer;
    begin
    Where := @TStaticThing.TheTimerProc;
    SetTimer(Handle, 0, 1000, Where);
    btnStop.Enabled := True;
    btnStart.Enabled := False;
    btnStop.SetFocus;
    end;

    //------------------------------------------------------------------------------

    procedure TfrmCallbackMain.btnStopClick(Sender: TObject);
    begin
    KillTimer(Handle, 0);
    btnStart.Enabled := True;
    btnStop.Enabled := False;
    btnStart.SetFocus;
    end;

    //------------------------------------------------------------------------------

    procedure TfrmCallbackMain.FormCreate(Sender: TObject);
    begin
    FStaticTest := TStaticThing.Create;
    end;

    //------------------------------------------------------------------------------

    procedure TfrmCallbackMain.FormDestroy(Sender: TObject);
    begin
    FStaticTest.Free;
    if btnStop.Enabled then
    KillTimer(Handle, 0);
    end;

    end.[/pascal]

    Now, this is a problem for you -- the WndProc will belong to the class rather than an instance of that class, which means that you need a way to figure out the particular instance for your window proc. Hence, if you want a standard method to be called then you'll have to figure out what instance is required in the wndproc first, and call it manually. You can't associate a standard method w/ the window class because, of course, that same method of the same object will be used by every CreateWindowEx'd window.

    The simplest method to do this is to add in some more info into your window class record. You'll notice that there is a field there called "cbWndExtra," which is usually set to 0. It's possible to use this to allocate memory per-window-created which will associate it with a particular instance of your class.

    So... just have the line "your_window_class.cbWndExtra := 4;" while setting up your window class. The 4 stands for 4 bytes extra, which is the size of a pointer.

    Now, remember that classes are pointers, which is pretty handy here. A function exists called "SetWindowLong" (also SetWindowLongPtr according to MSDN, which is newer, but I have no idea where that's declared). The SetWindowLong pointer lets you fill up the extra per-window information with whatever you want (meaning "the current instance of your class"). Like this:

    [pascal][background=#FFFFFF][comment=#0000FF][normal=#000000][number=#C00000][reserved=#000000][string=#00C000]SetWindowLong(FWindow, 0, Integer(Self));[/pascal]

    The first parameter is an HWND for your window -- this would be a member of your class. The second parameter is the offset into the additional bytes that you've allocated. This is only useful if you've allocated lots of memory. Here we want the first integer of info, which starts at offset 0. Finally, we do the important part and associate the current instance with the window.

    You'd do that after calling CreateWindowEx.

    Once you've done this, you can use GetWindowLong to retrieve the instance. Here's an example WndProc from one of my projects:

    [pascal][background=#FFFFFF][comment=#0000FF][normal=#000000][number=#C00000][reserved=#000000][string=#00C000]function WndProc(hWnd: HWND;
    message: UINT;
    wParam: WPARAM;
    lParam: LPARAM): LRESULT; stdcall;
    var
    ThisApp: TGLApplication;
    begin
    // The WM_SYSCOMMAND message is not in the case because if it is,
    // other messages won't be handled
    if message = WM_SYSCOMMAND then
    begin
    case wParam of
    SC_SCREENSAVE, SC_MONITORPOWER:
    begin
    Result := 0;
    Exit;
    end;
    end;
    end;

    ThisApp := TGLApplication(GetWindowLong(hWnd, 0));

    case message of
    WM_ACTIVATEAPP:
    begin
    if ThisApp <> nil then
    ThisApp.Active := LongBool(wParam);
    Result := 0;
    end;
    WM_CLOSE:
    begin
    if (ThisApp.Window <> 0) and not DestroyWindow(ThisApp.Window) then
    begin
    WriteLog('TGLApplication.ClearUpWindow: Could not destroy the window', ltError);
    //ThisApp.Window := 0;
    end;
    Result := 0
    end;
    WM_DESTROY:
    begin
    PostQuitMessage(0);
    Result := 0;
    end;
    WM_SIZE:
    begin
    if g_Screen <> nil then
    g_Screen.Resize(LOWORD(lParam), HIWORD(lParam));
    Result := 0;
    end
    else
    begin
    Result := DefWindowProc(hWnd, message, wParam, lParam);
    end;
    end;
    end;[/pascal]

    Notice the WndProc isn't associated with the class yetl. However, it's declared in the implementation section of the class rather than the interface section so that it's not visible to the outside world and, of course, it's deeply tied to the implementation of the particular class ("TGLApplication" here). As you can see, my choice above is a (to me) acceptable compromise -- have the window proc route the messages as appropriate to the wanted instance. Can't say that I've tried out having several apps for obvious reasons.

    If you can figure out how to get the windowproc into a class function successfully then I'd love to hear how you did it! I've tried the same trick as with above in the first example but CreateWindowEx always throws up . The WndProc itself gets called inside of its class, so I think I got close there...
    "All paid jobs absorb and degrade the mind."
    <br />-- Aristotle

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •