Tuesday, August 9, 2011

AX2012 Managed Code - Part 2: Interop Bliss

Part 1: Get Set Up
Part 2: Interop Bliss
Part 3: Unfortunate Limitations

In part 1 of this series, we setup some base code in AX, and set up Visual Studio with a project containing some AX proxy classes, ready to deploy and run code from Visual Studio. This article (part 2) builds on top of that code.


Ready for some action? I thought so! First, we'll create a pre/post event handler and hook it up to our Run() method as a managed pre handler. So, with the code from the previous article in place, let's add some code in Visual Studio. If you didn't save a solution you can re-open, you can open the Visual Studio project form the AOT. Open AX, and in the AOT go to Visual Studio Projects > C Sharp Projects and find the DAXMusings project. Right-click the project and select "edit". This will open the project in Visual Studio for you.
So, in the HandlerClass.cs file, we will add a new method to the HandlerClass. Handler methods (whether they are X++ or managed) that you will add in the AOT, have to be static and public of course, so they can be called from the event sender. We will use a "standard" pre/post handler, not a lightweight handler, so the only argument we are expecting is XppPrePostArgs. For more information on XppPrePostArgs and how to use it, also see my models and events series (specifically the second part). Just to see our code working I retrieve the calling object (getThis()) from the XppPrePostArgs class and output the type to the infolog using our Global class proxy. The interesting piece here is that getThis() returns an "object". We need to get this into our proxy. Remember the proxy is a "wrapper" around the AX object, and that means we can't CAST the getThis() returned object to our DAXMusingsEvents class, but we need to create a new proxy class and pass it the AX object. It's important you understand why and how this works, because it can lead to some confusion if you're not careful.



Let's build and deploy this to our client so we can add it as a handler in the AOT. Just press F5 in Visual Studio. In AX, open the AOT and find our DAXMusingsEvents class. Right-click on the Run() method and select "New Event Handler Subscription". Open the properties for the handler. Give it a proper name (this is important, check this post so see why, also check the models and events series), and set the "EventHandlerType" property to "Managed". For the class name, we need to include the full namespace handle to the class. So the class property will be set to "DAXMusings.ManagedHandlers.HandlerClass". As soon as you do that, the dropdown for the "Method" property will show you the "compatible" methods on the class. Just select the "PrePostHandler" method and save and compile the class.





If you now run your class, you should see the follow infolog:



If you started AX using F5 in Visual Studio, you can put a breakpoint in your C# method and run the class in AX again, to see how the debugging works.
So, this works perfectly. You could re-implement the code from the models and events articles to use a managed handler for the validateField instead of the X++ handler.

Let's try some regular interop. How about we make a C# method which we will call from X++ code? We'll create one method here which we can later re-use for other purposes. Close the AX client and go back into Visual Studio. In the HandlerClass, we will add the following method:



Hit F5 to build, deploy and start AX. Find your DAXMusingsEvents class in the AOT, and right-click > New > Method. We'll call the method "CallManagedCode". Inside, we will create an instance of an XppPrePostArgs class, set an argument "string", and call the static C# method we created previously.



Next, we'll call this method from the existing Run() method. Open up Run() and add the call to "CallManagedCode". When you now run the class, you should see the PrePost handler fire, but also our call to our method.




Let's review what this means. The "CallManagedCode" method, passes "this" (DAXMusingsEvents instance) and an instance of the XppPrePostArgs. Our C# managed method, accepts our PROXY classes for DAXMusingsEvents, and our proxy for XppPrePostArgs. As you can see from the output in the infolog, this works perfectly! You get the proxies in your C# code as expected. I encourage you to play around with this. Debug, try to create a new instance of an AX class in C# and return it from a method, etc. Interop is great. These were basically the missing features in previous versions of AX. Instances of CLR objects and/or AX objects crossing the boundaries back and forth.

As cool as this is, we'll have to end on a sad note in our part 3 of the series, showing you the unfortunate limitations of these new features.

5 comments:

  1. Real AX novice here. When you say 'run the class'...

    I have break point in the code (both in the VS and AX code) but it doesn't appear to be reaching them. Am I missing a step?

    Thanks,
    Kevin

    ReplyDelete
  2. Dear Joris,

    I deployed two assemblies to my dev environment.

    One is called: MyCompany.dll
    One is called: MyCompany.MyApps.dll

    I can reference correctly the first one (and all the types contained) but not the second one ... I guess because it contains points in the name of the assembly...

    Could you help me?

    Thanks,
    Ivan

    ReplyDelete
  3. Solved! It wasn't a problem of dots but only that I had to put in AX all the assembly referenced by that one and I missed one.

    ReplyDelete
  4. Dear Joris,

    with the latest AX 2012 R2 + CU7 code base, XppPrePostArgs class extends Object instead of XppEventArgs.

    What would be the right way now to create pre/post handler in managed code since we cannot add XppPrePostArgs class from Application Explorer to our Visual Studio project?

    Any sample codes to do this? Appreciate your time to post all these great infos!

    Jason

    ReplyDelete
    Replies
    1. I have not tried recently but I would suggest to either try to accept an Object and then cast it back to an xppprepostargs (make sure to use AX's namespace object and not the default .NET one). Or, you can try a simple hack to create a proxy for a kernel class, as explained here:
      http://daxmusings.codecrib.com/2011/06/ax2012-net-proxies-for-kernel-objects.html

      Sorry for the extremely late reply - I was hoping to test this but still haven't gotten around so hopefully this suggestion is still useful (and hopefully it works).

      Delete