As you know, one of the ways that you can customize your D365 for Operations installations is to add pre- and/or post handlers to existing X++ methods. These handlers are executed prior to, and after the method gets executed. Handlers are added using a declarative approach, by decorating the method with the PreHandlerFor and PostHandlerFor attributes.
There are some restrictions on which methods are eligible for handling this way: The methods have to be public or have to be marked with the [Hookable(true)] attribute. This is because the lengthy boilerplate code is not something you want every single method to have.
Now, the handlers must have a particular profile; they must be static, have no return value (i.e. be void methods), and must have exactly one parameter, of type XppPrePostArgs, as shown below:
[PostHandlerFor(formstr(MyForm), formMethodStr(MyForm, init)]public static void postHandler(XppPrePostArgs args)
Object thisForm = args.getThis( ); thisForm.disableButton1( );}
The XppPrePostArgs type has methods for accessing the parameters passed to the original method (useful for both pre- and post handlers), and the resulting value (which is useful only for post handlers). In the example above, the getThis() method is called to get a reference to the form where the original method is defined so the disableButton1() method can be called.
So far, so good. But how does it work?
The code in the handler has no idea what type the original method was. As shown, the this pointer can be accessed through getThis(), but the handler can be called from multiple methods from different classes or forms. The getThis() returns a value of type Object. You may then wonder how the disableButton1() call can be made; the compiler does not know the actual type!
This works because the compiler will not do early binding, as it does for method calls on most types. Early binding means that the compiler will do all the resolution of the method against the methods available on a particular type, so that it can generate the IL code that uniquely identifies the correct method. If it is not able to find such a method, an error ensues.
For certain types (the full list is: CLRObject, object, COM, COMVariant, ActiveX, FormActiveXControl, FormHTMLControl, and FormRun) the compiler will accept both early binding (for methods defined on the type) and any method name with any number of parameters passed. The resolution will happen at runtime, not at compile-time. This kind of resolution is a mixed blessing: It provides a lot of flexibility for the user, but it has disadvantages:
It is unfortunate that the user is not alerted to the fact that the compiler is trusting that the user knows what he is doing here. Perhaps we should change the intellisense to show a hint that the type accepts late binding - That is a topic for another time.
how pack and unpack record pre/post method binding for object write?
Would it be more performant to cast the result of the "getThis" method in order for the compiler to make an early binding ?
I guess you can use normal practice by creating interface that your form will implement, and then you will be able to cast getThis() to a specific interface, having early bindings all the way. Maybe Peter can comment if that would be the way to go?
I know it's been a while since these posts but, what if the form isn't yours? Likely the reason you are using a pre/post handler. Can't add an interface and no strong types to cast FormRun to.