tags:

views:

814

answers:

2

How can I compare the value of a variable that contains a pointer to a function with a function address?

I'm maintaining some code, and it is failing in Delphi 2007. The declaration is:

var
  EditorFrameWindow: Function: HWnd Of Object = Nil;

In a form activation, I've got:

procedure TEditForm.FormActivate(Sender: TObject);
begin
  EditorFrameWindow := GetFrameWindow;
end;

And in the form deactivation I've got:

procedure TEditForm.FormDeactivate(Sender: TObject);
begin
  if EditorFrameWindow = GetFrameWindow then
    EditorFrameWindow := nil;
end;

So what is happening is that the form is being deactivated twice, and it is failing as nothing else got activated. The FormDeactivate is called, it matches, and the EditorFrameWindow global is set to (nil,nil) (according to the debugger). Then it is being called again, and the function stored in the variable is called, but of course there isn't one stored so it jumps through nil and creates an exception.

What should I do to stop this happening? (The framework has been changed to a tabbed system, so the operation probably changed.)

+9  A: 

Would

procedure TEditForm.FormDeactivate(Sender: TObject);
begin
  if Assigned(EditorFrameWindow) and (EditorFrameWindow = GetFrameWindow) then
    EditorFrameWindow := nil;
end;

work per chance?

Edit:

You don't compare function addresses, you compare the results of those functions. So even though the fixed code above can no longer cause an exception it may still not do what you want it to. Another function that returns the same result would also reset the event handler.

To really check whether the variable is set to a specific event handler you will need to compare both elements in the TMethod record. Something like:

procedure TEditForm.FormDeactivate(Sender: TObject);
begin
  if (TMethod(EditorFrameWindow).Code = @TForm1.GetFrameWindow)
    and (TMethod(EditorFrameWindow).Data = Self)
  then
    EditorFrameWindow := nil;
end;
mghie
Note that while this works I don't like the code, and would therefore redesign it.
mghie
@mghie, yes, it may not be ideal, but as is the case with maintaining code that you don't fully understand, sometimes you don't want to touch it more than needed!
mj2008
+4  A: 

There are two ways you might want to compare method pointers. Method pointers consist of two pointers, a code pointer and an object pointer. Delphi's native way of comparing method pointers compares only the code pointers, and it looks like this:

if @EditorWindowMethod = @TEditForm.GetFrameWindow then
  EditorWindowMethod := nil;

It checks whether the code pointer in the EditorWindowMethod variable matches the starting address of the GetFrameWindow method in TEditForm. It does not check whether the object reference in EditorWindowMethod is the same as Self. If you want to make the the object references are the same, too, then you need to break apart the method pointer into its constituent parts with the TMethod record, which Mghie's answer demonstrates. (And you probably do want to compare the object references since it sounds like you have multiple edit forms. They all have the same GetFrameWindow code pointer, but they have different object references.)

The reason for the @ in the code is to tell the compiler that you want to refer to the method pointers. Without it, the compiler will try to call the method pointers, and that's what was getting you into trouble. The first time the window was deactivated, you called EditorWindowMethod and compared the resulting window handle with the return value from calling GetFrameWindow. They matched, of course, so you unassigned EditorWindowMethod. The next time the form was deactivated, you tried to call EditorWindowMethod again, but it was a null pointer.

You should consider getting rid of your dependence on activation and deactivation notification. Instead, simply check whether the form is active inside GetFrameWindow.

Rob Kennedy
+1 This is one of those messy questions (issues with legacy code) that becomes valuable because the answers are so informative.
Argalatyr