Link to home
Start Free TrialLog in
Avatar of wamoz
wamoz

asked on

Convert method ptr to proc addr for callback

I know this can be done. I remember seeing some code that DOES it in the VCL, but I'll be damned if I can find it again. Can anyone remember what this support function is called?
Avatar of dwwang
dwwang

For an API to use your procedure as a callback function, you only need to pass its address to it:

procedure myproc(...);
begin
end;

MyAPI(@myproc,...)
Avatar of wamoz

ASKER

I want to pass an object *method*.

Method pointers and procedure pointers are not compatible.

I quote the Delphi help:
"Global procedure pointer types and method pointer types are always mutually incompatible. In other words, a global procedure or function cannot be assigned to a method pointer variable, and a method cannot be assigned to a global procedure pointer variable."

Although the reason for this incompatibility is not stated, I can tell you that this is because calls to methods also pass context info - essentially a handle to the calling object, so that the method knows which instance data to use.
However, I see no much difference between them, this is from Delphi's VCL source:

  TClientDataSet = class(TDataSet)
  private
  ...
    function CalcFieldsCallBack(RecBuf: PChar): DBIResult; stdcall;
  ...
  end;

Check(FDSBase.SetFieldCalculation(Integer(Self), @TClientDataSet.CalcFieldsCallback));

Or maybe you can make it more clear?

Regards,
Wang
Avatar of wamoz

ASKER

I want to pass an object *method*.

Method pointers and procedure pointers are not compatible.

I quote the Delphi help:
"Global procedure pointer types and method pointer types are always mutually incompatible. In other words, a global procedure or function cannot be assigned to a method pointer variable, and a method cannot be assigned to a global procedure pointer variable."

Although the reason for this incompatibility is not stated, I can tell you that this is because calls to methods also pass context info - essentially a handle to the calling object, so that the method knows which instance data to use.
Avatar of wamoz

ASKER

Whoops, sorry about the double post.

I think you may be onto something -

@myobj.methname may well resolve to a method call on a specific instance...

I'll try it and let you know.
By the way, if you call the function within an object's body,
you can still use @myProc, since it's identical to @myobj.myproc.

Regards,
Wang
Avatar of wamoz

ASKER

I hope so, because I just tried @myobj.myproc and Delphi spat out a compiler error 'Variable required'.
Avatar of wamoz

ASKER

I finally worked out why @myobj.myproc is illegal.

The required syntax is @myCLASS.myproc
wamoz - you are almost correct. I hope this will clear it up:

Delphi has a type for method instances, its TMethod defined as a record with two fields: code and data both pointers. The reason for this (as you have already surmised) is that a method is (usually - I'll detail this out later) only sensible in the context of an object (= class instance), thus you need to know both where the method is in the class (the code pointer) and where the instance of the object is (the data pointer).

You could, using the RTTI, extract the TMethod information for a method in an object and pass the code member of that structure to the call back function. This would in fact be equivalent to your last comment (ie: @myCLASS.myproc).

However, there is one last gotcha (which is why I said usually above) and that is the callback should be a class method and it must not reference any data members (either directly or via other methods it calls).

I hope this helps.

Cheers, Raymond.
Avatar of wamoz

ASKER

Ray, it makes things clearer but it doesn't do me any good.

What I'm actually trying to *do* is turn a Delphi form into an AppBar. I found some info in MSDN in a Visual Studio topic called Application Desktop Toolbars, but as I may have remarked this is a not really designed for Delphi and in typical sloppy MS code style, uses a bunch of global variables.

I can do it like this, but if anyone tries to instantiate two copies of the form all hell will break loose due to the unmanaged violation of encapsulation.

As you are undoubtedly aware, a form is an object that instantiates a bunch of owned objects (controls) and the form unit is an instance auto-created on loading the unit due to the VAR clause in the interface section of the unit.

I'm trying to implement the business of registering and managing the form window as an appbar using SHAppbarMessage() in methods of the form class.

What's messing me up is the need to pass a callback address for notification messages to whatever it is that manages appbars.

Any suggestions?

I have just noticed that the callback is told the hWnd of the window in question so I suppose I can cope, I just think it's a shambles of a way to write code.
ASKER CERTIFIED SOLUTION
Avatar of rwilson032697
rwilson032697

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Raymond is right.

However, the problem is because of the question you asked, if you initially ask about the SHAppbarMessage(), I can have the correct answer too :-)

Below is a fully working code in Delphi(from UDDF):

--------------------------------------------------------------------------------------

// This needs to be in your public declarations @ the top of the pas file
procedure TForm1.IconCallBackMessage( var Mess : TMessage ); message WM_USER + 100;

procedure TForm1.FormCreate(Sender: TObject);
var
   nid : TNotifyIconData;
begin
     with nid do
     begin
           cbSize := SizeOf( TNotifyIconData );
           Wnd := Form1.Handle;
           uID := 1;
           uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
           uCallbackMessage := WM_USER + 100;
           hIcon := Application.Icon.Handle;
           szTip := 'This is the hint!';
     end;
     Shell_NotifyIcon( NIM_ADD, @nid );
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
   nid : TNotifyIconData;
begin
     with nid do
     begin
           cbSize := SizeOf( TNotifyIconData );
           Wnd := Form1.Handle;
           uID := 1;
           uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
           uCallbackMessage := WM_USER + 100;
           hIcon := Application.Icon.Handle;
           szTip := 'This is the hint!';
// All the above is probably not needed.
     end;
     Shell_NotifyIcon( NIM_DELETE, @nid );
end;

procedure TForm1.IconCallBackMessage( var Mess : TMessage );
var
   sEventLog : String;
begin
     case Mess.lParam of
// Do whatever you wish here. For example popup up a menu on a right click.
          WM_LBUTTONDBLCLK  : sEventLog := 'Left Double Click';
          WM_LBUTTONDOWN    : sEventLog := 'Left Down';
          WM_LBUTTONUP      : sEventLog := 'Left Up';
          WM_MBUTTONDBLCLK  : sEventLog := 'M Dbl';
          WM_MBUTTONDOWN    : sEventLog := 'M D';
          WM_MBUTTONUP      : sEventLog := 'M U';
          WM_MOUSEMOVE      : sEventLog :=  'movement';
          WM_MOUSEWHEEL     : sEventLog := 'Wheel';
          WM_RBUTTONDBLCLK  : sEventLog := 'r dbl';
          WM_RBUTTONDOWN    : sEventLog := 'r down';
          WM_RBUTTONUP      : sEventLog := 'r up';
     end;
end;
--------------------------------------------------------------------------------

Regards,
Wang
Avatar of wamoz

ASKER

Ray,

I've got it to work and the doco is VERY misleading.
It *is* a callback proc address, there are about four messages it passes and the way I got the MS code to work sensibly was to create a unit and treat it as a notional Flyweight object for which all state is extrinsic (qv Design Patterns).

Actually I think this is what the coder had in mind. Percieved like that it is much less a mess and doesn't depend on the class of the form, so maybe I owe someone an apology.

Now I have a new issue - full-window drag causes horrible flickering when docking because my code detects end-of-window-drag via WM_WINDOWPOSCHANGED and you get one of those for every redraw under full-window drag.

I'd really like to discuss this with someone - you, preferably, since you seem to comprehend API programming and to be moderately iinterested in the question.

I'm quite happy to share my code with you. My email address is peterw@wamoz.com, so if you're prepared to engage in a private email exchange please drop me a line.
Hi wamoz.

Its interesting that you say the callback is a proc address. Is this a dual purpose field (dwwang posted complete source which treats it as a window message...)

I am not an API guru, though I am happy to contribute in any way I can.

As an initial thought, you could intercept the WM_ERASEBKGRND (spelling?) message and clobber it while dragging. This should have the effect of eliminating the flicker (assuming it is caused by continual redrawing while dragging the window).

Cheers,

Raymond.

Avatar of wamoz

ASKER

I finally have the horrid thing working.

MS uses a callback to deliver messages because some of the messages must be handled synchronously and using SendMessage would lock up the message queue.

In a sense I have provided my own workaround to the initally stated problem. I know there is an answer to the question because I have seen it, but nobody's been forthcoming in that respect, and we've all moved on from there.

I have created a project that does nothing but manage a form as an AppBar. It can dock/undock, has inertia and correctly performs various housekeeping required by Windows, such as converting certain WM messages to ABM messages and forwarding them via SHAppBarMessage, and hiding the AppBar during desktop tiling and cascading operations.

The code is a bit epic to post here, so I've made it available from my website:

http://www.wamoz.com/wombat/applicat.htm

By the way, chaps, my name is Peter.
Peter: I'll understand if you reject my answer - it sounds like you had a really torrid time with this!

Cheers,

Raymond.