views:

76

answers:

3

I'm effectively trying to deserialize a form.

One of the objects on the serialized form has a method which takes a series of events as parameters.

Now since I don't have the class type of the object when I'm deserializing, I have a method on the object doing the deserialization called AddMethod which is declared like this:

procedure TMyDeserializer.AddMethod(ControlName, EventName: String;
  MethodAddr: Pointer);
var
    TargetControl : TControl;
    Method : TMethod;
begin
    if Not Assigned(TempForm) then
        Exit;
    if TempForm.Name = ControlName then
        TargetControl := TempForm
    else
        TargetControl := TempForm.FindChildControl(ControlName);

    if Assigned(TargetControl) then
    begin
        Method.Code := MethodAddr;
        Method.Data := TargetControl;
        SetMethodProp(TargetControl, EventName, Method);
    end;
end;

So that I can poke subroutines into the various controls as I deserialize them, The problem is I need to add events as a list of parameters (not to a control). e.g.

SetUpEvents(EventHandler1:TNotifyEvent;EventHandler2:TNotifyEvent);

Where EventHandler1 and EventHandler2 are defined somewhere in code as

Procedure EventHandler1(Sender:TNotifyEvent);  
begin
    // Do something
end;

These are not methods but stand alone subroutines.

When I'm assigning these to objects the subroutine doesn't need to be part of an object as the AddMethod procedure handles it with a call like

MyDeserializerInstance.AddMethod('Button1','OnClick',@EventHandler1);

This works for standard event handlers, such as Button1.OnClick but not if I want to do

Procedure SetUpButton1Click(Method: TNotifyEvent)
begin
    TButton(MyDeserializerInstance.TempForm.FindChildControl('Button1')).OnClick = Method;
end;

The problem is I can't pass the subroutine as a method to the example Set Up Procedure.

The form being created isn't declared in an interface and is entirely defined by the file it is read from as well as a few stand alone routines in code.

So I suppose the question is how do turn a subroutine into a method at run time (after creating the object it is supposed to be part of), and if I can't do that how do I pass the subroutines in code as parameters in another method?

So far I've tried casting a TMethod as the correct event type and filling in the .Data as the TempForm. It called the correct method but it scrambled the parameters.

Delphi version is 2007

A: 

I'm sure someone will correct me if I'm wrong but I don't believe there is a way to create a type definition at runtime in native Delphi. Delphi's RTTI just doesn't handle this yet.

The two scenarios that come to mind for object serialization are persistence and IPC. (There may be more that I haven't thought of).

Delphi's DFM serialization would be an example of persistence. If you look at a dfm you'll notice it isn't defining methods at all. It's simply assigning event handlers to properties expecting an event handler. Both the handlers and the properties are defined at design time using normal type definitions.

If your intent is IPC(whether on the same machine or a remote one) there are already existing frameworks for accomplishing this. (RemObjects comes to mind)

codeelegance
A: 

You don't "make a method" at run time. That would amount to compiling new code. The methods that you assign to various event properties need to already exist.

Furthermore, you can't "add events." The object you're deserializing already has events. You defined them when you wrote the class declaration in your Delphi code. You can't add new event properties to a class after it's been compiled.

It appears that what you're really saying is that you have a standalone procedure that you happen to have named Method1, and you want to pass it as a TNotifyEvent parameter when you call SetUpMethods.

That's the wrong way to go. That Method1 procedure isn't a method, despite its name, so you mustn't use it where a method is required. Change that declaration so it belongs to a class, and then it will be a method.

If you don't want to have to instantiate the class that the method belongs to, that's fine — you can declare it as a class method instead:

class procedure TSomeClass.Method1(Sender: TNotifyEvent);

I encourage you to change the declaration of AddMethod so that the last parameter is of type TMethod. Then you're sure to have both the code and data portions of the method pointer. Right now, you're assigning the data portion based on the object whose event property you're assigning, but as I mentioned in my comment, it's rare for that relationship to exist, especially now that the method belongs to an entirely unrelated class (TSomeClass in my example). The value of the TMethod.Data field becomes the Self value when the method gets called. It's your responsibility to ensure that the value you store in that field is of a compatible type for the class the code belongs to.

Rob Kennedy
if TMethod.Data becomes self, surely TMethod.Data would become the TempForm?
JamesB
No, because you are not assigning TempForm to the TMethod.Data field, you are assigning the TargetControl instead. Thus, the TargetControl will become its own Self for the event handler (which is a duplicate, since the event's Sender parameter will already point at the same TargetControl).
Remy Lebeau - TeamB
+4  A: 

Non-static class methods have a hidden Self input parameter that is filled in when the method is called. That is what the TMethod.Data field corresponds to. In order to use a standalone procedure as a handler for an event that expects a class method, the procedure must have an extra parameter defined to represent the Self parameter so the value of TMethod.Data has somewhere to go, ie:

procedure Button1ClickHandler(Self: Pointer; Sender: TObject);
begin 
  // Do something 
end; 

MyDeserializerInstance.AddMethod('Button1', 'OnClick', @Button1ClickHandler);

Your AddMethod() implementation is assigning the TargetControl as the TMethod.Data value, so the Self and Sender parameters above will end up pointing at the same object at runtime, but that is OK.

Without the explicit Self parameter defined, that explains why your parameters are getting "scrambled" when the procedure called at runtime. The hidden Self value is being assigned to the Sender parameter, and the real Sender value is being ignored.

Remy Lebeau - TeamB
Thanks, that works nicely. If I add the Self parameter and get the DeserializationInstance to return a TMethod with the TempForm as the Data then I can cast the result as any event e.g. a NavBeforeEvent to pass in as parameters
JamesB
Assigning the TempForm to the TMethod.Data is not a requirement for the event handler to work in general. It simply means you will have access to both the TempForm object and the TargetControl object as parameters to the event handler (you can also use GetParentForm(Sender) to get the TempForm object from the TargetControl Sender as well).
Remy Lebeau - TeamB