PDA

View Full Version : Linking a procedure to an object for remote execution



WILL
08-06-2008, 07:00 AM
Hey guys... I'm working on a tiny gui unit for use in my game projects. One of the things I'd like it to do is associate external procedures and execute them if assigned for say a button click.

Assigning them would be as simple as

MainMenu.Button1.OnClick := YourProcedure;

YourProcedure() would be created by the user of the library. And Button1 would be a control object that resides inside the window object (MainMenu).

procedure YourProcedure;
begin
// whatever
end;

my code to detect mouse clicks on the button object would run the 'linked' procedure from it's own code. How can I do this? Can I simply use pointers?

JSoftware
08-06-2008, 07:25 AM
It's pretty easy in FreePascal. I don't know how you would do it in Delphi though..

FPC:
procedure YourProcedure; [public, alias: 'YourProcedure'];
begin
//Do something. This procedure is in no way visible to the procedure using it
end;

//In your code
procedure YourProcedureDecl; external name 'YourProcedure';

...
begin
MainMenu.Button1.OnClick := @YourProcedure;
end;

Is this what you were asking? :P

WILL
08-06-2008, 07:33 AM
Hmm... not sure. Have you ever used JEDI-SDL's audio mixer?

You pass a procedure to this external procedure

procedure Mix_HookMusicFinished( music_finished : Pointer );

and whatever procedure you passed to music_finished will automatically execute when the song ends. all I have to do is add this line of code for my own written procedure.

Mix_HookMusicFinished(@RestartMusic);

RestartMusic is a procedure of my own making.

Brainer
08-06-2008, 08:12 AM
I once tried this:

type
TMyEvent = procedure;

// In DLL
procedure MyDLLFuncIdle(IdleFunc: TMyEvent);
begin
FIdleFunc := IdleFunc;
end;

// In External App
procedure MyDLLFuncIdle(IdleFunc: TMyEvent); external 'MyDLL.dll';

procedure xxxIdleFunc;
begin
// do some shizzle here
end;

begin
MyDLLFuncIdle(xxxIdleFunc);
end;


And it worked fine for me. ;)

WILL
08-06-2008, 08:20 AM
Hmm... well what is FIdleFunc though?

I've just found something on Delphi's ExecMethod but nothing seems to exist like it for FPC. :?

Something like ExecProcedure(Func: pointer); would be great. :lol:

User137
08-06-2008, 08:25 AM
Events in normal TButton etc are very simple.

type
TNotifyEvent = procedure(Sender: TObject) of object;

// .. and then it is put as a variable
FOnClick: TNotifyEvent;
// .. and as a property if want to make a component event of it
property OnClick: TNotifyEvent read FOnClick write FOnClick;


TNotifyEvent = procedure(Sender: TObject) of object;
Here " of object" tells that the procedure must be class procedure, not a standalone procedure. Those are defined like:
TNotifyEventLonely = procedure(Sender: TObject);

You can manually make a procedure and very easily set it (as long as the parameters match the defined type):
FOnClick:=YourClickProcedure;

And in your class you can then call it like
if assigned(FOnClick) then FOnClick(self)

Brainer
08-06-2008, 10:13 AM
Hmm... well what is FIdleFunc though?
Let's assume you have a procedure inside your DLL, that does certain things with the variable FIdleFunc.

A little example

// -- GLOBAL --
type
TMyMathEvent = procedure(Z: Single);

// -- DLL --
var
FMathFunc: TMyEvent;


procedure DLLDoIt;
var
x: Single;
begin
x := 3.14;

if Assigned(FMathFunc) then
FMathFunc(X);
end;

procedure AssignMathFunc(TheMathFunc: TMyMathEvent);
begin
FMathFunc := TheMathFunc;
end;

// -- EXTERNAL APP --
procedure AssignMathFunc(TheMathFunc: TMyMathEvent); external 'MathDLL.dll';

const
Radius = 3.225;

var
TheArea: Single;

procedure CalculateCircleArea(Z: Single);
begin
TheArea := Z * (Radius * Radius);
end;

begin
AssignMathFunc(CalculateCircleArea);
end;

JSoftware
08-06-2008, 12:10 PM
Will, could you try to explain a little better what you want to do?

There's a difference between calling methods and calling normal functions/procedures.

If you want to handle non-object procedures/functions you simply pass either a pointer or a type of procedure/function:


type
TProcedure = procedure; //Already declared in system

..
procedure SomethingLength(callBack: TProcedure);
begin
...
Callback; //Calls callback
...
end;

If you have a pointer you would like to call you simply typecast it to TProcedure and call it :)


TProcedure(funcPointer);


As long as you are using fastcall or stdcall you are safe using this approach

De-Panther
08-06-2008, 01:08 PM
I think he wants to set procedures for events of objects he create during the runtime of his game/program

like:

objname:=TLabel.Create(self);
objname.SetOnClick:=Procedurename;


I don't think this would work(I know it won't work).
but I think that this is what he wants

De-Panther
08-06-2008, 01:09 PM
I think he wants to set procedures for events of objects he create during the runtime of his game/program

like:

objname:=TLabel.Create(self);
objname.SetOnClick:=Procedurename;


I don't think this would work(I know it won't work).
but I think that this is what he wants

User137
08-06-2008, 03:50 PM
objname:=TLabel.Create(self);
objname.SetOnClick:=Procedurename;
Yes this works, why would it not? All events in delphi are based on this.

Edit: I mean works for procedures, don't know about external ones. Delphi allows pointers to procedures too though.

De-Panther
08-06-2008, 04:40 PM
objname:=TLabel.Create(self);
objname.SetOnClick:=Procedurename;
Yes this works, why would it not? All events in delphi are based on this.

Edit: I mean works for procedures, don't know about external ones. Delphi allows pointers to procedures too though.

realy?
never tryed this...

De-Panther
08-06-2008, 05:17 PM
objname:=TLabel.Create(self);
objname.SetOnClick:=@Procedurename;


and thanks IK for the help
http://ik.homelinux.org

WILL
08-06-2008, 05:32 PM
I want to mimic the functionality of JEDI-SDL's sdl_mixer function Mix_HookMusicFinished where all I do is supply the memory address of the regular procedure or function I want to run and have my GUI objects run them as they see fit.

What I have in the end is

1) the gui unit already written for the user and all it does is runs the assigned procedure/function to the onevent-like hook.

2) the user's game code that needs to be written and just needs to run a function or simple assign of his/her written procedure's address to link it to the gui system.


This is more about me being able to run a procedure from it's memory address than it is about using the DOM which I want to avoid as much as I can for size issues. This isn't for any kind of Windows App.

This must be an available syntax for FPC please keep in mind as I'm using Lazarus.

WILL
08-06-2008, 05:43 PM
Hmm... well what is FIdleFunc though?
Let's assume you have a procedure inside your DLL, that does certain things with the variable FIdleFunc.

A little example

// -- GLOBAL --
type
TMyMathEvent = procedure(Z: Single);

// -- DLL --
var
FMathFunc: TMyEvent;


procedure DLLDoIt;
var
x: Single;
begin
x := 3.14;

if Assigned(FMathFunc) then
FMathFunc(X);
end;

procedure AssignMathFunc(TheMathFunc: TMyMathEvent);
begin
FMathFunc := TheMathFunc;
end;

// -- EXTERNAL APP --
procedure AssignMathFunc(TheMathFunc: TMyMathEvent); external 'MathDLL.dll';

const
Radius = 3.225;

var
TheArea: Single;

procedure CalculateCircleArea(Z: Single);
begin
TheArea := Z * (Radius * Radius);
end;

begin
AssignMathFunc(CalculateCircleArea);
end;


ok I'm still a little sure about all this FIdleFunc and FMathFunc stuff... are these just system variables? What do they derive from and are they documented anywhere? And do they automatically run whatever function was assigned to them?

Legolas
08-06-2008, 06:07 PM
As far as I have seen, Mix_HookMusicFinished is used in this way;

Mix_HookMusicFinished(YourProc)

well, at least something like that :lol:

The example made by JSoftware works fine with free pascal too if you do
SomethingLength(@YourProc);

WILL
08-06-2008, 06:55 PM
Ok using JSoftware's example... expanding on it a little... could I do this?


type
TProcedure = procedure; //Already declared in system

var
Bob: TProcedure;

procedure RunProc(callBack: TProcedure);
begin
...
Callback; //Calls callback
...
end;

procedure StoreProc(proc: TProcedure; var StoredProc: TProcedure);
begin
...
StoredProc := proc;
...
end;

procedure MyOwn;
begin
...
end;

begin
StoreProc(MyOwn, Bob);
RunProc(Bob);
end.

I'll try an experiment to see if this goes... seems like it might...

JSoftware
08-06-2008, 07:01 PM
You can't do that in Freepascal. It works fine in Delphi but Freepascal will complain that procedures don't return anything. You have to get the address of the procedure:

StoreProc(@MyOwn, Bob);

WILL
08-06-2008, 07:12 PM
so if I pass the addresses in it'll work fine then you think?

WILL
08-06-2008, 07:38 PM
Ok follow-up question...

What if I tried to run the store procedure but there was not one assigned? I'd need a flag to keep track of if one was stored or not, right?

WILL
20-06-2008, 07:19 PM
Finally got finishing up with my test run. It seems to work rather well actually. :) So big thank you to JSoftware! ;) (And of course everyone else)

I had to use a wee bit of voodoo to get it to store the procedure addresses for later use though. No biggie however as it was easy as pi...

type
TGUIControl = class(TObject)
...
hasOnClick: Boolean;
OnClick: TProcedure;
procedure GetOnClick(proc: TProcedure);
end;

procedure TGUIControl.GetOnClick(proc: TProcedure);
begin
hasOnClick := True;
OnClick := proc;
end;

So thats how I did it.

JSoftware
20-06-2008, 10:08 PM
Hmm why not just assign OnClick with nil in your constructor and then have
if assigned(OnClick) then OnClick();
when you want to call the procedure?

Edit:
I would do it like this:

type
TGUIControl = class(TObject)
private
fOnClick: TProcedure;
public//or published if you like
...
property OnClick: TProcedure read fOnClick write fOnClick default nil;
end;

WILL
21-06-2008, 05:20 AM
Yes, I supposed I could simply do that. However I wasn't sure that the Nil value would stick after some memory got moved around... much like floating point values shift and change on assignment.

Almindor
22-06-2008, 10:45 AM
FPC is 100% compatible to delphi in Delphi mode (no need for @ altough using @ is much nicer IMHO, makes things clear).

What you're describing are basic callbacks.

Say you got:


procedure SomeProcedure;
begin
// user defined stuff
end;

var
StoredProc: TProcedure; // defined in system, but you can type specific ones as you need


An you want to assign SomeProcedure to StoredProc. If you use delphi mode you do:

StoredProc := SomeProcedure;

in FPC modes (e.g.: ObjFpc) you do:

StoredProc := @SomeProcedure;

If you use OOP, you need to use "procedure of object" type for the procedure variable e.g:



type
TUserClass = class
public
procedure OnClick;
end;

TCallBack = procedure of object;

var
StoredProc: TCallBack;



Assignment is then basically the same, you just use [@]object.method;

You can check if it's assigned by checking the StoredProc for nil (no need to set it to nil in objects or globals, both FPC and Delphi nil their variables in these cases) e.g.:



if Assigned(StoredProc) then
StoredProc();



Notice the (). They are important especially if the StoredProc is a function returning something valid (a pointer would be a nice trap). To make sure the compiler stores the result of the function CALL instead of it's address (in delphi mode only, FPC has it clear) you need to put the () there even if it doesn't take address.

For example:



type
TStoredFunc = function: Pointer;

var
StoredFunc: TStoredFunc; // let's say it got assigned elsewhere
p: Pointer;

begin
if Assigned(StoredFunc) then
p := StoredFunc; // error in delphi mode, p will contain address of StoredFunc
end;