PDA

View Full Version : Problems when using Callbacks



ddaedalus
15-05-2007, 04:18 PM
Hi,

I'm trying to realize a Callback using TFPList ( many Procs should be able to hook themselves up ) . But when I try to add a Procedure to the List i get a:


WidgetDesktop.UserEventCallbackList.Add(@UserEvent );
...
infor.pp&#40;74,59&#41; Error&#58; Incompatible type for arg no. 1&#58; Got "<procedure>", expected "Pointer"


FreePascal fixes_2_2, quite recent svn

Thanks in advance
Bartek

Chebmaster
15-05-2007, 04:42 PM
Maybe they added the "Typed @ operator" compiler setting to improve Delphi compatibility, and it is turned on?

ddaedalus
15-05-2007, 05:31 PM
Thx, I didn't know a compiler directive like this existed. But:


1.1.59 $T or $TYPEDADDRESS &#58; Typed address operator &#40;@&#41;
In the &#123;$T+&#125; or &#123;$TYPEDADDRESS ON&#125; state the @ operator, when applied to a variable, returns
a result of type ÀÜT, if the type of the variable is T. In the &#123;$T-&#125; state, the result is always an untyped
pointer, which is assignment compatible with all other pointer types.

It only applies to variables. Nevertheless I tried turning it off. Didn't help.

jdarling
15-05-2007, 05:47 PM
Bartek, can you post up a simple sample application that shows the same problem that you are having. If we had that I'm sure you would get an answer or a fix much faster.

The other option would be to show us the definition of your method that your adding and the block of code that is adding it. This way we have a bit more context then just a single line.

Callbacks usually take the form of:type
TMyCallBackMethod=procedure(SomeVar:AnsiString);

procedure AMethodToCallback(SomeVar:AnsiString);
begin
// Do something
end;

begin
// Adding to list some other method
whatindex := FMyProcList.Add(@AMethodToCallback);
// Calling
FMyProcList.Methods[whatindex]('This is a test');
end;

As I said, if this doesn't help, then post up a simple sample and see what you get back. Building the simple sample may also help you find your solution :)

ddaedalus
15-05-2007, 06:15 PM
Sorry, I thought i made a very stupid error, therefore I didn't post more. Seems like my problem is slightly more complex than i thought.


&#123;$mode objfpc&#125;&#123;$h+&#125;
program test;
uses sysutils, classes;
type
TCallback = procedure&#40;param1&#58; integer; param2&#58; TObject&#41; of object;

TBlub = class
constructor Create&#40;cbl&#58; TFPList&#41;;
destructor Destroy; override;

procedure Callback&#40;param1&#58; integer; param2&#58; TObject&#41;;
end;

constructor TBlub.Create&#40;cbl&#58; TFPList&#41;;
begin
cbl.Add&#40;@Callback&#41;;
end;

destructor TBlub.Destroy;
begin
inherited Destroy;
end;

procedure TBlub.Callback&#40;param1&#58; integer; param2&#58; TObject&#41;;
begin
writeln&#40;'Callback'&#41;;
end;

var
CallbackList&#58; TFPList;
blub&#58; TBlub;
begin
CallbackList&#58;=TFPList.Create;
blub&#58;=TBlub.Create&#40;CallbackList&#41;;
TCallback&#40;CallbackList.items&#91;0&#93;^&#41;&#40;0, nil&#41;;
end.
...
bartek@bartek-laptop&#58;/tmp$ fpc test.pp
Free Pascal Compiler version 2.1.3 &#91;2007/05/04&#93; for i386
Copyright &#40;c&#41; 1993-2007 by Florian Klaempfl
Target OS&#58; Linux for i386
Compiling test.pp
test.pp&#40;16,19&#41; Error&#58; Incompatible type for arg no. 1&#58; Got "<procedure>", expected "Pointer"
test.pp&#40;37&#41; Fatal&#58; There were 1 errors compiling module, stopping
Fatal&#58; Compilation aborted
Error&#58; /usr/local/bin/ppc386 returned an error exitcode &#40;normal if you did not specify a source file to be compiled&#41;

This should represent what I'm trying to do. Don't be offended because of my hacky accessing of items and not freeing ;)

bartek

jdarling
15-05-2007, 06:58 PM
Ahh... The problem is that you are trying to place a class method pointer into a standard list. This causes problems due to the fact that the class instance is lost when the method is called. Thus FPC (and Delphi for that matter) don't know how to resolve the calling structure.

You can hack around this if you are careful not to reference self within you callback methods, but I refuse to show you as it can break things that you never thought about :).

The best option is to move callback outside of the object itself and make it a standard procedure. Then you can add it to the list and call the callback with the object reference directly. Here is a sample made from your code:program CBTest1;

{$mode objfpc}{$H+}

uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes
{ add your units here };

type
TBlub = class
constructor Create(cbl: TFPList);
destructor Destroy; override;

end;
TCallback = procedure(self : TBlub; param1: integer; param2: TObject); // Changed to normal method

procedure Callback(self : TBlub; param1: integer; param2: TObject); // Define method and add a self reference for useability
begin
writeln('Callback: ', integer(pointer(self)), #9, param1, #9, integer(pointer(param2))); // Just to show it works
end;

constructor TBlub.Create(cbl: TFPList);
begin
cbl.Add(@Callback); // Register
end;

destructor TBlub.Destroy;
begin
inherited Destroy;
end;

var
CallbackList: TFPList;
blub: TBlub;
i: integer;
begin
CallbackList:=TFPList.Create;
blub:=TBlub.Create(CallbackList);
for i := 0 to 9 do // Just to show it works
TCallback(CallbackList.items[0])(blub, i, nil); // Remove ^ you don't need to de-reference the pointer
Callbacklist.Free; // Cleanup
blub.Free; // Cleanup
readln();
end.

Of course, if you don't need the object pointer, then don't use objects or simply remove the self option. In the end this performs the same action and has little more overhead on implementation :).

ddaedalus
15-05-2007, 08:09 PM
Ok, I see. Thanks. Is my understanding right, that I would by able to use this, If i made a customized ( in fpc generics, when generics would work ) list?
Your way solving this is good, and doesn't requiere me to rewrite things. Nevertheless you made me curious. How would I be able to hack around this. :twisted: ( "hack to learn" ^^ )

Thanks again ;)
bartek

jdarling
15-05-2007, 08:37 PM
It requires non-crossplatform assembly code. The best way to learn is to look at Pascal Script for Delphi http://www.remobjects.com/page.asp?id={9A30A672-62C8-4131-BA89-EEBBE7E302E6}). Basically you have to push the pointer to the class instance onto the stack, then push the method address and variables onto the stack. Call the method and check the stack and registers for return values. Finally cleanup after yourself. Honestly, this isn't something I fell comfortable explaining more then just pointing to a sample, as far as I know there is no other way that will work. I can show you code that will compile, but it will blow up when you run it :).

Actually, here is code that will compile (in theory you would only have to put the method pointer above the calling of the actual method in practice it doesn't work):program cbtest2;

{$mode objfpc}{$H+}

uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes
{ add your units here };

type
TMyCallback = procedure(intval1 : integer; obj : TObject) of object;
PMyCallback = ^TMyCallback;

{ TMyCallbackList }

TMyCallbackList=class
private
fMethods : TList;
function getCount: integer;
function GetMethod(index: integer): TMyCallback;
public
constructor Create;
destructor Destroy; override;

function Add(WhatMethod : TMyCallback) : integer;
property Method[index:integer]: TMyCallback read GetMethod;
property Count : integer read getCount;
end;

{ TMyObject }

TMyObject = class
public
constructor Create(WhatList : TMyCallbackList);

procedure ACallback(intval1 : integer; obj : TObject);
end;

{ TMyObject }

constructor TMyObject.Create(WhatList: TMyCallbackList);
begin
WhatList.Add(@ACallback);
end;

procedure TMyObject.ACallback(intval1: integer; obj: TObject);
begin
writeln('Testing the callback method: ', integer(pointer(self)), #9, intval1, #9, integer(pointer(obj)));
end;

{ TMyCallbackList }

function TMyCallbackList.getCount: integer;
begin
result := fMethods.Count;
end;

function TMyCallbackList.GetMethod(index: integer): TMyCallback;
begin
result := TMyCallback(fMethods[index]^);
end;

constructor TMyCallbackList.Create;
begin
fMethods := TList.Create;
end;

destructor TMyCallbackList.Destroy;
begin
fMethods.Free;
inherited Destroy;
end;

function TMyCallbackList.Add(WhatMethod: TMyCallback): integer;
begin
fMethods.Add(@WhatMethod);
end;

var
ml : TMyCallbackList;
o : TMyObject;
i : Integer;
begin
ml := TMyCallbackList.Create;
try
o := TMyObject.Create(ml);
try
for i := 0 to 9 do
ml.Method[0](i, nil);
finally
o.Free;
end;
finally
ml.Free;
end;
end.