Skip to main content

Issues concerning X++


I was looking at the API that we publish for the X++ compiler, and it struck me that it would be really easy to implement a script host for X++. This is a program that allows you to execute arbitrary X++ code that is stored in files in the file system. In this way, you can use X++ as a systems programming language, starting things at particular times etc. I thought it would be fun to see what it takes to implement that command line tool. In the spirit of sharing, I am listing the C# code below. It is, after all, only just over 100 lines of code. Let's imagine that we have a file called MyFile.xpp containing the following:
real f(int i, real r, str s)
{
    ;
    System.Console::WriteLine("Hello world");
    return i + r + strlen(s);
}
With the script host installed, you can do things like:
C\> XppScriptHost  MyFile.xpp 3  3.141 "I am an argument".
The script host will return a code to the operating system, so that decisions can be made in script files etc. You could even register the file type (Xpp in this case) with the operating system, so that clicking on the xpp file will automatically start the script (but you will not be able to pass parameters). This is really easy in Windows: You just right click in the .xpp file, and the system will ask you to identify the program to use to open the file. Select this tool and you're in business.
There is certainly room for improvement to this: You could introduce parameters for each of the four arguments passed to the logon call if that is useful in your scenarios. I did not do so here for the sake of simplicity.
Since the business connector is compiled against an earlier version of .NET (to be compatible with the sharepoint pieces), you will need to tell the command line tool that it is should support the earlier framework. You cannot use .NET 2, because the LINQ stuff in the code would not run in that case. Feel free to rewrite that if you must. I just use a configuration file (app.Config) with the following content:
xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>

Here is the source code in all its glory
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Microsoft.Dynamics.Ax.XppScriptExecutor
{
    using Microsoft.Dynamics.BusinessConnectorNet;
    using System.IO;
 
    /// 
    /// Command line tool to evaluate X++ snippets as functions provided in a 
    /// file that is provided as parameter. Any number of parameters may follow,
    /// and these are used as parameters to the method. The result is written to 
    /// stdout. 
    /// 
    class XppScriptHost     {         ///          /// Entry point to the console application.         ///          /// "args">The arguments passed from the command line.         ///          /// "bullet">         /// 0 if all was well         /// 1 if the file was not found         /// 2 if the script contains errors         /// 3 if the some other error happened         ///          ///          static int Main(string[] args)         {             if (args.Length < 1 || args.Contains("-help"StringComparer.OrdinalIgnoreCase))             {                 System.Console.Error.WriteLine("XppScriptHost filename parameter...");                 System.Console.Error.WriteLine("    The filename must denote a file containing an X++ function.");                 System.Console.Error.WriteLine("    The following arguments are interpreted as parameter values.");                 System.Console.Error.WriteLine("    The result returned from the X++ function is printed on stdout.");                 System.Console.Error.WriteLine("    The X++ code can use System.Console::WriteLine(...) to print output");                 System.Console.Error.WriteLine("");                 System.Console.Error.WriteLine("XppScriptHost -help");                 System.Console.Error.WriteLine("    Writes this message.");                 return 0;             }                          var source = string.Empty;             try             {                 using (var stream = new StreamReader(args[0]))                 {                     source = stream.ReadToEnd();                 }             }             catch (Exception e)             {                 System.Console.Error.WriteLine("File " + args[0] + " could not be opened.");                 System.Console.Error.WriteLine(e.Message);                 return 1;             }             Axapta ax = new Axapta();             try             {                 ax.Logon(nullnullnullnull);                 AxaptaObject xppCompiler = ax.CreateAxaptaObject("XppCompiler");                 var success = (bool)xppCompiler.Call("compile", source);                 if (!success)                 {                     // An error occurred during compilation. Get the error messages.                     var messages = xppCompiler.Call("errorText"as string;                     System.Console.Error.WriteLine(messages);                     return 2;                 }                 // The compilation proceeded without error. Now execute it.                 // Push parameters on the X++ stack                 xppCompiler.Call("startArgs");                 foreach (var arg in args.Where(arg => !arg.StartsWith("-")).Skip(1))                  {                     int ival;                     decimal dval;                     if (arg.StartsWith("\"") && arg.EndsWith("\""))                     {                         xppCompiler.Call("setStrArg", arg.TrimStart('"').TrimEnd('"'));                     }                     else if (int.TryParse(arg, out ival))                     {                         xppCompiler.Call("setIntArg", ival);                     }                     else if (decimal.TryParse(arg, out dval))                     {                         xppCompiler.Call("setRealArg", dval);                     }                     else                      {                         xppCompiler.Call("setStrArg", arg);                     }                 }                 xppCompiler.Call("endArgs");                 var o = xppCompiler.Call("executeEx");                 // Write the return value to stdout.                 System.Console.Out.WriteLine(o.ToString());                 return 0;             }             catch (Exception e)             {                 System.Console.Error.WriteLine("Error during execution");                 System.Console.Error.WriteLine(e.Message);                 return 3;             }             finally             {                 ax.Logoff();             }         }             } }

Popular posts from this blog

Dynamics Axapta: Sales Orders & Business Connector

Well, again folllowing my same idea of writting close to nothing and pasting code, I'll paste in some code to create a sales order from some basic data and the invoice it. I'll try to explain more in the future. AxaptaObject axSalesTable = ax.CreateAxaptaObject("AxSalesTable"); AxaptaRecord rcInventDim = ax.CreateAxaptaRecord("InventDim"); AxaptaRecord rcCustTable = ax.CreateAxaptaRecord("CustTable"); rcCustTable.ExecuteStmt("select * from %1 where %1.AccountNum == '" + MySalesOrderObject.CustAccount + "'"); if (MySalesOrderObject.CurrencyCode.Trim().Length == 0) MySalesOrderObject.CurrencyCode = rcCustTable.get_Field("Currency").ToString().Trim(); string sTaxGroup = rcCustTable.get_Field("taxgroup").ToString().Trim(); //set header level fields axSalesTable.Call("parmSalesName", MySalesOrderObject.SalesName.Trim()); axSalesTable.Call("parmCustAccount", M

Passing values between form and class

Class name is EmplDuplication and Form is EmplTable . void clicked() {    MenuFunction mf;    args args = new Args();    ;     args.record(EmplTable);     mf = new menufunction(identifierstr(EmplDuplication), MenuItemType::Action); mf.run(args); } Meanwhile, in the main() method of the EmplDuplication class, we need to put this Axapta x++ code to get the datasource: static void main(Args args) {     EmplDuplication EmplDuplication; EmplTable localEmplTable; ;     if(args.record().TableId == tablenum(EmplTable)) localEmplTable = args.record();     ... }