PDA

View Full Version : Pointer to functions in a class



Fernando
14-05-2003, 09:13 PM
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.

Useless Hacker
14-05-2003, 10:09 PM
When you say it doesn't work, do you mean it doesn't compile, or what?

Fernando
14-05-2003, 10:36 PM
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?

Useless Hacker
15-05-2003, 01:10 PM
You can't pass a method as a callback function, because methods have a hidden parameter pointing to the object, i.e,
procedure TYourClass.YourMethod(Stuff: Integer);
is really:
procedure TYourClass.YourMethod(Self: TYourClass; Stuff: Integer);
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. :)

Alimonster
15-05-2003, 08:28 PM
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:

[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.

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:

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

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:

[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;

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...