Welcome to MSDN Blogs Sign in | Join | Help

Issues concerning X++

This blog deals with issues about X++
Forthcoming changes to the X++ language

High level programming languages and their compilers solve many problems for developers who want to provide value to a particular business domain.

 

For one thing, compilers raise the abstraction level from the nitty-gritty details of the hardware and software systems running the code (like storage locations and memory allocation) to something more manageable (like tables representing customers etc). Another value proposition is to recognize as many errors or other problems in the code as possible; the more problems that are diagnosed this way (i.e. at compile time), the fewer problems end up as expensive and embarrassing bugs at runtime, i.e. when the product is deployed at the customer site. The X++ compiler has built-in diagnostics for hundreds of error situations, and the MorphX environment features a pluggable system (the best practice rules) that you can use to express rules that are important to you. We ship a large set of these best practice rules out of the box. Many of these compensate for problems that could just as easily be diagnosed by the X++ compiler itself; not using these seems ill advised.

 

We are continually looking at what we can do to make the value proposition of the X++ language stronger. We have identified that several changes are needed to avoid a number of runtime problems, and to make future alignment with managed languages possible. As always, we realize that any changes we make to the core language are a mixed blessing to people who maintain a large code base: On one hand the new semantics cause the compiler to catch more errors earlier, but on the other hand they will invariably cause changes to existing code.

 

In the next release of the Dynamics Ax product we will make several changes to the core X++ language, and even though the release of this is certainly not imminent it makes good sense for the development community to be aware of the changes that are in the pipeline. There is no reason to make things worse by writing new code that does not comply with the new, stricter rules.

 

The changes are described below. This list may not be conclusive: We will probably identify more such issues in the coming months.

 

Covariance vs. Contravariance

Currently, X++ allows overriding methods to supply signatures (including the return type) containing types that are derived from the types supplied in the defining method: The signatures do not need to list identical types. Consider the case below where the MyMethod method does provides a derived type (Derived) in the override in lieu of the type given in the defining method (Base):

class Base
{
    Base MyMethod(Base b) {…}
}

class Derived extends Base
{
    void DerivedMethod() {…}
    Derived MyMethod(Derived d) { d.DerivedMethod(); }
}

This raises some critical considerations. Consider the very common case involving tables (that are all derived from Common):

class Base
{
    public void foo(common b) {…}
}

class Derived extends Base
{
    public void foo(SalesTable st)
    {
        st.CanBeDirectlyInvoiced(); // Calling a method on SalesTable
    }
}

When the following is attempted:

{
    CustTable ct; // Something other than SalesTable
    BaseClass bc = new DerivedClass();
    bc.foo(ct); // This method is never called???
}

The foo method is never actually called. We will remedy this by imposing the restriction that an overriding method must have identical parameter types has the method it overrides.

 

No compilation error is issued when parameters without default arguments follow default arguments.

This case is a simple laxness in the compiler, that currently accepts methods that have parameters with and without default values mixed, as shown below:

public client server RouteOpr routeOpr(
    ItemId          itemId,
    ConfigId        configId,
    RouteOpr        routeOpr = null,
    InventSiteId    siteId)
{
   
}

The intention is to have the parameters having default values as the last parameters, so the actual parameter values can be omitted in the call. The current behavior of the compiler is to simply ignore the default argument values. In the future the compiler will correctly diagnose the situation, and the application must be changed to comply with the rule that non-default parameters cannot follow default parameters:

 

public client server RouteOpr routeOpr(
    ItemId          itemId,
    ConfigId        configId,
    RouteOpr        routeOpr,
    InventSiteId    siteId)
{
   
}

 

The X++ compiler allows two methods with the same name where one is static and the other not.

X++ is quite happy to have a method by a given name as both an instance method and a static method if the two methods are not within the same class:

class Base
{
    void int MyMethod(int i) {…}
}

class Derived extends Base
{
    static void MyMethod(str s) {…}
}

This is reasonable in X++ because the two calls are syntactically different: The static call is done using the "::" operator and the instance call is done using the "." operator. However, this situation causes lots of confusion and will be deprecated. The workaround is to simply rename one of the methods, typically the static one.

 

No visibility rules are enforced for access of overriding methods

In the future, overriding methods must be at least as accessible as the defining method, not less. So, consider:

class Base
{
    public void MyMethod() {}
}

class Derived
{
    protected void MyMethod() {}
}

It does not make sense to have MyMethod (which is virtual, as all X++ methods) as a protected method in the derived class, because it can always be called from the outside using a reference to Base:

Base b = new Derived();

b.MyMethod(); // calls Derived method, even if marked protected or private.

So

  1. If a method is marked as private, no overrides are allowed. (as today).
  2. If a method is marked as protected, the compiler will only accept protected or public overrides.
  3. If a method is marked as public, the compiler will only accept public overrides.

 

Abstract methods can be reached through a SUPER() call.

When a derived class overrides an abstract one, the derived implementation may call super(), essentially calling a method that has no body and can return no value. If the method does not return void, the X++ interpreter will complain (at runtime) because the abstract method does not return a value:

class Base
{
    abstract int MyMethod() {}
}

class Derived extends a
{
    int MyMethod() ( return super(); } // Calling abstract method??
}

However, if the return type had been void, the situation would not have issued any problems at runtime. In the future, the compiler will diagnose this with a compile time error.

 

Static constrictors are not diagnosed

The X++ compiler currently accepts the static keyword on constructors:

static void new ()
{
    info("");
}

This is wrong because a static constructor is never called. The fact that this is not disallowed by the compiler can lead programmers to think that static constructors (which after all is a very meaningful concept in many OO languages) are implemented, which is not the case. There are very few cases of this in the application that we ship, but the compiler will be fixed to diagnose the error with a suitable error message and the application must be fixed up accordingly.

 

Abstract class implementation.

The semantics for abstract classes in Ax and other modern languages (like C#) are very different. In the C# the first non abstract class is required to implement all the abstract methods defined by the abstract super classes. In X++, there is no such requirement: The only requirement in X++ is that all abstract methods are implemented for any types that are instantiated - This can be done at any derivation level. Any error messages relating to non-implemented abstract methods are issued when a type instantiation is attempted (using the new operator).

 

In the future the semantics in the X++ compiler will be changed to match the semantics of C#. The changes are as follows:

 

When a concrete (i.e. a non-abstract) class is encountered, the implementation must:

  1. Traverse the hierarchy from most basic to most derived maintaining a set of methods: When an abstract method is defined, it is entered into the set. When an abstract method is implemented (in either a concrete class or an abstract class), it is removed from the set. This will identify a set of methods that have not yet been implemented.
  2. All the methods in the set calculated as described above must be implemented in the current class. If this is not the case, an error message is issued.

All the instances in the application code base where a concrete class does not implement the abstract methods defined in its immediate abstract supertype must be modified.

 

Interfaces.

The same issue as described above holds for implementing interfaces. Currently it is not required in Ax that all methods defined in an interface be implemented in the class implementing the interface, as it is in C#.

A similar algorithm to what is listed above must be employed to maintain the C# semantics. The interfaces situation is a little more complex because interfaces can also be arranged in hierarchies of their own.

 

Open array assignments.

X++ features several types of arrays. One type allows the programmer to declare the array without providing an upper bound on the number of elements in the array.

{
    int a[];
    int b[];

    b = a; // No effect
}

 

This works well for single arrays but assignments involving these values have no effect. The compiler will diagnose this case in the future. 

 

We have prepared a set of best practice checks that diagnose most of these issues. Running these best practices on your code base should give you an idea of how much needs to be changed in your code base. Keep your eyes peeled at his site for these upcoming best practice checks.

 

Step-by-Step Checklist for Debugging Batch Jobs in Dynamics Ax

This post applies to Microsoft Dynamics Ax 2009 (see version note below).

 

A. Enable Global Breakpoints

 

            When debugging in the client, you want to set breakpoints that can be caught by the debugger.  The following steps show how.

 

1.      Launch the Microsoft Dynamics AX Configuration tool from the Start > Administrative Tools menu on your client computer. The first time this tool opens, you will be on the Original configuration. Create a new configuration named Debug. Click Manage and choose the Create Configuration menu option.

 

2.      Enter a new name such as Debug, and choose copy from Original configuration and click Ok.

 

3.      A new configuration is created and shown in the configuration tool.  Click the Developer tab.

 

4.      Choose the following options:

·         “Enable global breakpoints to debug code running in the Business Connector or client” (required for this scenario)

·         “Enable user breakpoints to debug code in the Business Connector” (optional for this scenario)

 

Note: Take special care that the Configuration Target remains set to Local Client since this is the only client you can set global breakpoints from.

 

 

B. Configure Your AOS to Run Under Your User Credentials

 

1.      Launch the Services utility on your AOS server machine, and choose the AOS instance that you are configuring for debugging.

 

2.      Right-click on your chosen AOS instance name and choose Properties. On the properties window, choose the Logon tab. Select “This account” for the Logon As option.  Enter your full Domain account {e.g. contoso\YourName } and password credentials.  This account must be the same user account as the account used for debugging batch code.

 

3.      Click Apply once you have modified the user account.

 

4.      You will be prompted to restart the service. Choose yes to restart the service (note: ensure other users are not working on this service before restarting it).

 

 

C. Configure the Dynamics AX Server to Enable Debugging

 

1.      From your Start > Administrative Tools menu, open the Microsoft Dynamics AX Server Configuration tool. This tool has the same option to Manage configurations that you saw earlier in the client configuration tool.

 

2.      Choose the Manage and Create Configuration option to create a new server configuration. As before, copy from the original configuration and choose Debug as the name.

 

3.      Verify the AOS instance matches the instance you want to use for debugging.

 

4.      Choose the following options:

·         “Enable breakpoints to debug X++ code running on this server” (required for this scenario)

·         “Enable global breakpoints to debug X++ code running in batch jobs” (required for this scenario)

 

Note: When you click Apply, you will be prompted to restart the service, and you should choose Yes.

 

 

D. Set breakpoints and start debugging.

 

1.      Launch the Dynamics AX Client from the Start menu, and open the developer workspace.

 

2.      Open the class you would like to debug from the AOT and set breakpoints at desired locations.

 

3.      Launch the Dynamics Ax Debugger from the Start menu. The debugger must be opened directly in order to find and catch global breakpoints.

 

4.      Schedule the batch job to run. 

 

Example: To schedule work calendar delete to run in batch; launch the WorkCalendarDelete class in the AOT, click Batch tab, check Batch Processing check box and click Ok.

 

5.      Wait for the debugger to catch the breakpoints.

 

 

E. Version information.

Batch debugging on Windows Server 2003 works with Dynamics Ax 2009.  However, a hot fix is needed for Windows Server 2008 which is in development.  The next release of Dynamics Ax will include a fix for both operating systems (2003 and 2008).

Serializing Axapta Foundation Class instances as XML streams

The Axapta foundation  classes are a set of generally applicable classes that contain values of either simple types or other classes. They are:

  • Structs, structures of named fields,
  • Arrays, arrays of any type, not just simole type
  • Lists, lists allowing traversal with enumerators
  • Maps, dictionaries allowing lookup of values, yielding a result value
  • Sets, ordered groups where duplicates cannot occur.

The classes are generally well understood, so this is not going to be a tutorial on how to use them. This blog will deal with the infrastructure provided to allow serializing and deserializing values to XML streams, that can then be processed (searched with XPath queries, persisted in files or databases etc) as any other XML document.

 

In the example below we will be describing how this works for structs, but the same principles apply to the other foundation classes as well.

The struct class has a method that yields an XML representation of its content. Not suprisingly, this method is called Xml(). As you can imagine, this method can easily deal with instances of simple types, but structs are not limited to containing simple types: They can contain instances of any type defined by the user. Ideally we would like these classes to be able to participate in the serialization and subsequent deserialization. As it happens this is exactly what the infrastructure provides, with a little help from the developer.

 

When the xml method is called on the struct, the implementation will call xml (int indent) on any objects in the struct. It is then up to this implementation to serialize the state of the object into XML. Let's consider an example where a struct contains a user defined Point class, with the obvious set of x,y values. The class, listed below contains an XML method that returns a string containing the XML format of the state of the object. The indent parameter indicates the indentation level; padding with spaces as done in the example below is not really needed: There are no semantics for white space in XML, but it makes the XML document look good in the eyes of carbon based lifeforms.

 

class Point
{
    int x;
    int y;

    public void new(int _x, int _y) 
    {
        x = _x; y = _y;
    }

    public str ToString()
    {
        return strfmt("Point(%1,%2)", x,y);
    }

    public str xml(int indent=0)
    {
        return strrep(" ", indent) + strfmt("<Point x='%1' y='%2' />", x,y);
    }
}

 

If you use string operations like above, you should be careful to not include the XML metacharacters <,>,&,'," in the values, You should use the &lt;, &gt; &amp; &apos; and &quot; placeholders instead. You can use .NET to build the XML stream for you (it will escape the characters for you) or place content inside [CDATA[]] tags. For the example above this would clearly be overkill.

 

With this in place, you can serialize the content of a struct into an XML stream and work with it as an XML document:

 

static void structserializetest(Args _args)
{
    struct s = new Struct ("str name; int age; Point p");
    XmlDocument d = new XmlDocument();
    str xml;

    s.value("Name", "John Doe");
    s.value("Age", 42);
    s.value("Point", new Point(43, 77));
 

    xml = s.xml();

    // Load the XML we just serialized into an XML DOM

    d.loadXml(xml);

}

 

But that's not all. If you add a

 

static public CreateFromXml(XmlNode n)
{
     return new Point(
        str2int(n.attributes().getNamedItem("x").value()),
        str2int(n.attributes().getNamedItem("y").value()));
}

 

method to the class you will be able to create the point instance from deserializing the XML you just created:

 

// … continued from example above…

// Load the XML wejust serialized into an XML DOM

d.loadXml(xml);

 

copy = struct::createFromXML(d.documentElement()); // Serialize

 

// Show the results. Should match the original value.

print copy.definitionString();

print copy.toString();

 

 

 

 

Is the X++ Compiler Too Flexible?

In Microsoft Dynamics AX 2009, the X++ compiler is sometimes too flexible in its rules for code. It is likely at some of these flexibilities will be eliminated in future releases.

This blog entry describes some flexibilities of the X++ compiler that we recommend you not utilize.

 

 

[1] Sequence of Default Parameters

 

Recommendation: In a method declaration, declare parameters that have default values after the last parameter that has no default.

 

The X++ compiler and runtime currently allows you to disregard this recommendation, as the following code example demonstrates.

 

 

class MyClass

{

  static public int AddTwoNumbers

      (int _firstNum = 4

      ,int _secondNum)

  {

    return (_firstNum + _secondNum);

  }

}

 

 

  The following job calls the above AddTwoNumbers method. Notice there is no way to accept the default value of the _firstNum parameter.

 

 

static void Job1(Args _args)

{

  int iAnswer;

  iAnswer = MyClass::AddTwoNumbers(8 ,16);

  print( IAnswer );

  pause;

}

 

 

 

[2] Access Modifier on Methods

 

Recommendation: Each time you create a new method on a class, explicitly add an access modifier to the method declaration, meaning public, protected, or private.

If no access modifier is given, the default behavior is public.

 

 

 

[3] No Access Modifier on Classes

 

Recommendation: Do not add an access modifier to any class declaration.

The X++ compiler ignores the keywords public and protected and private on classes, so adding an access modifier can only confuse other programmers. In effect, all X++ classes are public.

 

 

 

[4] Put TODO First

 

Recommendation: The TODO should be the first non-whitespace after the start of the comment.

 

When the X++ compiler detects the string TODO in a comment, it lists a task on the Tasks tab of the compiler output window.  The compiler goes a little too far in trying to detect a TODO.  For instance, the TODO is detected in each of these two examples:

 

 

// Remember TODO Remove the diagnostic prints.

 

 

/* Important TODO:

Rewrite this SQL as a more efficient set operation.

*/

 

 

The two above examples would be fine if the first words were removed: it would be better to remove the first words Remember and Important.

Using the Cross company feature from the Business Connector.

In Ax 2009 the new cross company feature was introduced. It allows the programmer to specify a container containing strings denoting company names to the crosscompany hint:

container c = ['dat', 'dmo'];
select crosscompany: c * from custtable where custtable.Name == "Jones";

That is all very well in X++, of course, but how do you handle it when accessing data through the business connector?

The first approach would be to do something like the following:

AxaptaRecord r = axapta.CreateAxaptaRecord("CustTable"); 
ax.ExecuteStmt("select crosscompany: ['dmo', 'dat'] * from %1 where %1.Name == 'Jones'", r);

However, that will not work: The argument to the crosscompany hint is not an expression of type container, it is a variable of type container. Hence you cannot provide arbitrary expressions, only variable references. This was done to make the implementation of the feature simpler.

The solution lies in the realization that the string that enters the ExecuteStmt is actually entered into a function that is compiled and executed at runtime. There are no limits to what can be expressed in this string, as long as it is legal X++ statements.

So, consider doing:

AxaptaRecord r = axapta.CreateAxaptaRecord("CustTable"); 
ax.ExecuteStmt("container c = ['dmo', 'dat']; select crosscompany: c * from %1 where %1.Name == 'Jones'", r);

which would actually work.

 

 

 

The Enterprise Portal team introduces new blog...

The EP team has just created a blog about their stuff. These guys know their stuff, and are heavy users of managed code interoperability with X++. Check it out at http://blogs.msdn.com/epblog/.

 

Using graphics in forms and legacy reports.

Let's face it - The forms and reports in Dynamics Ax are pretty bland. They're very good at presenting information grouped in logical ways, but the information presented is invariably in the form of numbers and strings. The further you get up into the decision hierarchy the less the decision makers are concerned about details: Instead they are concerned about trends and ratios and key performance indicators that are easily consumed, even on the golf course.

 

So, if the old saying about a picture saying a thousand words is true, maybe it’s about time we put some pizzazz into forms and reports. The following is some work that I did for the recent Convergence 2008 conference in Orlando.

 

Basically the effort was about showing yet another area where managed code can be leveraged from X++. I chose to present rich information in charts both in forms and reports. In the forms case I chose a web service to provide the data to be shown in the form because it was consistent with the message that Steve Ballmer was presenting in his keynote at Convergence. The report graphics however were shown in a modified existing legacy report. Note, that the new reporting feature that we have added in Ax 2009 can do much more than what we are doing here, completely without any of the messy custom code needed in this approach. The approach does have merit though, because it allows an ISV to create some value for customers running on older versions of the product. If the target installation is really old (and does not support managed code from X++), then COM controls must be used instead. I’ll not go into detail about this in this blog about that approach though.

 

The examples here are implemented using a managed graphics library called Xceed (http://xceed.com/Chart_WinForms_Intro.html). This was the library at hand at the time: Its use does not imply an endorsement of this particular (or any other product). There are other high quality graphics rendering libraries in existence, some of them free, some of them not.

 

I will start by presenting the approach chosen for reports. The gist of it is: Collect the information to present in a pie chart while the report is being built. Then, use the graphics API to create a bitmap file. Bind the bitmap file to a bitmap control on the form. That’s all it takes.

 

On my machine, the Customer turnover report now looks like this:

The main changes are in the fetch method. You will find that two additional data structures are maintained: One (called Names) is simply a list of the account numbers to include in the pie chart; the other (called revenues) is a list of their revenues. That is all the information needed to actually generate the bitmaps (which is done in the calculateBitmap method on the report). Please refer to the xpo file below for further details.

 

I wanted to add some graphics to forms as well, but I did know of any place where it would make sense in the current application. I’m afraid that that is a testament of how little I know the application more than anything else. To drive home the point made by Steve Ballmer (about computing as services etc) I decided to use a web service to fetch information and display that in the form. The web service I chose is a demographics service from www.cdyne.com. Basically the service allows you to enter an address and get lots of interesting information about the neighborhood: The mean income and house value, the racial mix, mean age of inhabitants etc. I added the pie charts etc to the customer form to show all this information. You can see a screenshot below:

 

 

Note: The data shown in here is for the purpose of technology demonstration only. It may be illegal to use information about gender or race to target potential customers. Also, the data in the Dynamics Ax demo data was designed to look like real addresses, but for legal reasons they are not quite accurate. I put in some sample known addresses for the demo. The web service only covers the US.

 

You will find the code that draws the bitmaps in the file below:

The Xceed library is a commercial product that requires a license key. It can be obtained by buying their product, or by getting a 45 day demo license. Go to the xceed site (http://xceed.com/US_Suite_Intro.html) for details. The demographics service is also a pay service, but they have a demo license that can be used for this sort of thing.

You will need to add references to the xceed libraries in the references node in Dynamics Ax for the compiler to know about them. The libraries to add are:

xceed.chart
xceed.chart.core
xceed.chart.Datamanipulation
xceed.chart.Graphics2D
xceed.chart.GraphicsCore
xceed.chart.Standard
xceed.chart.UIControls
xceed.chart.Utilities

Please find the source code for the updated customer turnover report here:

The Custtable form with the demographics tab is found here:

 

Caveat: Using the += and -= operators for dates.

As you may know, it is possible in X++ to add integers to dates. The semantics are that the integer value is considered a number of days to add or subtract to the date.

{
   Date d;
   // ...
   d = d + 7; // Seven days later
   // ...
   d = d - 7; // Seven days prior.
}

The example above works well. However, the programmer may see the code above and decide that it can be expressed more succinctly by using the composite assignment operators +=  and -=. In the case above there is no gain by using these, but in cases where the lefthand side is very complex (involving lots of method calls with side effects) a case can be made for the argument that it should be replaced with += and -=.

The obvious thing to do then is:

{
   Date d;
   // ...
   d += 7; // Seven days later
   // ...
   d -= 7; // Seven days prior.
}

This too will work as expected. The problem occurs if you try to use the += operator to subtract days or the -= operator to add days; that is, supplying a negative number of days to add or subtract.

{
   Date d;
   // ...
   d += 7; // Seven days later
   // ...
   d += -7; // ERROR Seven days prior.
}

The problem is based in the fact that the compiler will generate code to do a type conversion from the integer argument to a date, which will succeed for positive values, but not for negative ones. It is not easy to fix, but in due course we will, either by disallowing the composite assignment operators on dates or by throwing an exception when the argument is negative.

To make matters even worse, the kernel will simply show a dialog box but continue execution with wrong data afterwards.

The learning from all this is that if you use these operators for dates, you must make sure the expressions on the right hand side always evaluate to non-negative values. If you cannot guarantee this, use the d = d + expr syntax instead.

 

 

 

Missing values: getting the System.Missing.Value value

When dealing with office integration scenarios and when calling APIs that are based on old COM interfaces, there  is a need to specify that a value is not provided. The notmal way to do this (say, in C#) is to use

System.Reflection.Missing.Value

Now, as it happens, Value is not a static property, as one might expect, but instead a public static field. It is currently not possible in X++ to reference the value of a field.

There are at least two ways to solve the problem:

  1. You can use a managed method to get the value. This has the disadvantage that you need to put this glue code in an assembly that is deployed to your Ax installation. This may or may not be a problem, depending on your situation, but it is not exactly elegant.
  2. Or, you can use reflection from X++ to get the value, as shown here:
   System.Type type = System.Type::GetType("System.Reflection.Missing");
   System.Reflection.FieldInfo info = type.GetField("Value");
   System.Object value = info.GetValue(null);

This has a performance penalty involved, but that can rpobably be ignored in most cases.

Macros - Definitions and Pitfalls

The X++ language features a macro expansion facility. With it, you can define macros, use macro values, do conditional compilation etc. In this blog I'll describe the semantics of the constructs and provide some guidance to resolve some of the problems beginners and experts alike are having with this language feature.

Macros are unstructured in that they are not defined by the grammar of the language. The handling of macros takes place before the text reaches the compiler.

Macros may appear inside methods and class declarations anywhere that white space is permitted, and may also appear after the ending } in class definitions.

The semantics of each of the macro keywords are described below:

Macro constructs

#define

The syntax is

#define.MyName(SomeValue)

This defines a macro called MyName with the value SomeValue. When this definition is in effect, any references to #MyName will be replaced with the character sequence SomeValue. The definition has no other semantics aside from defining the symbol MyName: The text does not reach the compiler itself. When the compilation of the current method is over, the symbol (MyName in this case) is no longer remembered. If the symbol is already defined, the old value is discarded and replaced by the new value.

#globaldefine

The syntax is

#globaldefine.MyName(SomeValue)

This has the same semantics as #define, described above.

#definc

The syntax is

#definc.MyName

This macro construct is used mainly when the value is a integer value. The preprocessor will increment the value of the symbol by one. If the value was not defined before the #definc occurs, an error is issued by the compiler. If the value before the #definc is not an integer, the old value will be overwritten with the value 0 and then incremented, yielding the value 1.

#defdec

The syntax is

#defdec.MyName

This macro construct is used mainly when the value is a integer value. The preprocessor will decrement the value of the symbol by one. If the value was not defined before the #defdec occurs an error is issued by the compiler. If the value before the #defdec is not an integer, the old value will be overwritten with the value 0 and then decremented, yielding the value -1.


#undef

The syntax is

#undef.MyName

The effect of this is to remove the symbol MyName from the list of current macro definitions. It is not considered an error to remove a symbol that has not previously been #defined.

#if ... #endif

The syntax is

#if.MySymbol
    …
#endif

Or

#if.MySymbol(SomeValue)
    …
#endif

In the first case the textual content marked with … in the examples above is inserted into the source stream if MySymbol has previously been defined. In the second case, the content is inserted into the source stream if and only if the symbol is defined and has the indicated value.

The #if constructs may be nested to any level but there is no #else construct.


#ifnot ... #endif

The syntax is

#ifnot.MySymbol
    …
#endif

Or

#ifnot.MySymbol(SomeValue)
    …
#endif

In the first case the textual content marked with … in the examples above is inserted into the source stream if MySymbol has not been defined. In the second case, the content is inserted into the source stream if the symbol is not defined or is defined but does not have the indicated value.

The #if constructs may be nested to any level. There is no #else construct.

#macrolib

The syntax is

#macrolib.MyName

The name must denote a node in the macros branch of the AOT. The text in that node is processed by the preprocessor. The net effect is to insert the content of the named macro in the source text where the directive appears. It is an error if the node is not found in the macros branch in the AOT.

#macro / #localmacro

The keywords #macro and #localmacro are interchangeable; there is no difference in the semantics of the two. This construct is used to define a symbol to denote textual content possibly spanning several lines.

The syntax is

#localmacro.MySymbol
    ….
#endmacro

Example:

class MyBaseClass extends Runbase
{
     int v1;
     #define.myMacro(“Hello world”)
     #localmacro.currentlist
        v1
     #endmacro

     public container pack()
     {
         return [#currentlist];  // #currentlist expands to v1
     }

     public void run()
     {
         print #myMacro;   // #myMacro expands to “Hello world”
     }
}

class MyDerivedClass extends myBaseClass
{
    int v2;
    #localmacro.currentlist
       v2
    #endmacro


    public container pack()
    {
        return [super(), #currentlist];  // #currentlist expands to v2
    }

    public void run()
    {
        print #myMacro;  // #myMacro expands to “Hello world”
    }
}

#MySymbol

The syntax is

#MySymbol

This inserts the value of the symbol into the source stream. It is an error to refer to a symbol that has not been defined.
If the name denotes a node in the macros branch of the AOT, the text in that node is processed by the preprocessor (in this case #MySymbol is a shorthand for #macrolib.MySymbol).

Common Problems

In this part of the blog post, I'd like to describe some of the problems programmers have when dealing with macros.

Macro parameters

It seems to be a little known fact and a source of some confusion that macros can be parametrized: Values can be given to the %<n> placeholders at the macro expansion site. Simple textual substitution of the positional parameter with the given, actual parameter then takes place. If no parameter is supplied, the empty string is used. So

#define.MyString("Hello World from %1")

will define a named macro (MyString) with one parameter. If that macro is expanded as shown below:

#MyString(X++)

the resulting string will be

"Hello World from X++"

Note that the place of expansion did not supply the letters X++ inside quotes. That would have generated a compiletime error:

#MyString("X++")

would have generated

"Hello World from "X++""

That is what is meant by simple string substitution.

The confusion can also occur because the % notation is also used for parameter substitution in the strFmt string formatting function. As you know, this function has a variable length parameter list, and each reference of %n in the first argument (a string) will be expanded to contain a textual representation of the n'th argument, as shown below:

print strfmt("The value is %1", theValue);

Now, some programmers have been known to want to specify the first argument, i.e. the string containing the substitutions, with a macro symbol:

#define.TheText("The value is %1")
print strfmt(#TheText, theValue);

But, the macro substition kicks in before the compiler sees the source code. The macro substition engive will not find a parameter to place where the %1 is, so the compiler will see:

print strfmt("The value is ", theValue);

which is probably not what the programmer intended.

Macros in class declarations

Some confusion stems from the situation where macros are defined in class declarations. In order to understand how this is handled it is useful to review what the compiler does when it compiles a method. It starts by calculating the sequence of class derivations that the class is part of. It then parses each of the class declarations with the least derived one first, filling its internal symbol table with the macros as it goes along. After compiling the most local class declaration (the most derived one) the compiler compiles the method itself. Any symbol defined in any of the class declarations will subsequently be available for use in the methods. Symbols defined in a class declaration may be replaced by values defined in more derived class declarations.

Parenthesis in macro strings

The scanner dealing with macro strings is quite simple minded. It will not handle the situation where closing parenthesis characters are included in the string. So,

#define.Another("(This is text in parenthesis)")

will generate a compiler lexical error. If you need to do this, you should use the #localmacro directive instead:

#localmacro.Another
    "(This is text in parenthesis)"
#endmacro

In this context the end of the macro is signalled by the #endmacro string, not a right parenthesis.

Entering sales orders over the phone...

At the convergence conference in Copenhagen I wanted a demo that could demonstrate another way of integrating with Ax: I went ahead and wrote a demo that leverages the Speech Server (2007) to accept spoken commands via the telephone to enter sales orders. As it happens this has some customer value apart from the purely technical demonstration: nearly everyone has access to a telephone, but not everyone has a smartphone or a phone that is capable of interfacing with the internet. Those that do often have user interfaces that are not rich enough for entering sales orders. It would be useful to allow the use of a phone, portable or otherwise, with a simple keypad and the spoken word to enter orders.

 

It turns out that there is a toolkit from MS that is very suitable for implementing the kind of application I was contemplating: the Microsoft Office Communications Server 2007 Speech Server SDK. In fact this framework is a large superset of what is actually needed for this application. In a production environment you would have a Office Communications Server 2007 that is hooked up to the incoming phone line by a piece of dedicated hardware (a Voice-over-IP/VoIP gateway). The speech application is then run in the Windows Workflow Foundation (WF) running on the Speech Server. The Speech Server (2007) Developer Edition (download from http://www.microsoft.com/downloads/details.aspx?FamilyId=BB183640-4B8F-4828-80C9-E83C3B2E7A2C&displaylang=en)  leverages the Windows Workflow Foundation in that it takes the form of a series of activities that are linked together in a dialog workflow that describe the phone conversation. The system features a rich drawing surface where these activities can be wired together and their properties set.

 

You may find more about the Speech Server by going into the MSDN Forums at http://forums.microsoft.com/unifiedcommunications/default.aspx?siteid=57. You will find people there who can address questions you may have, both technical and from a product perspective. Also, there is a lot of really solid information to be found on http://gotspeech.net/.

 

The workflow that was created for this demo is discussed below. Please bear in mind that the scenario may not be very realistic; it exists to present the underlying technology, not to solve a customer problem. The flow is as shown in workflow. The code for the demo can be found in http://cid-b2f15fddcf82cd4e.skydrive.live.com/self.aspx/Public/VoiceResponseOrdering2.zip.

 

As you can see, the system will start up by saying hello and welcome to the user phoning in (after setting up the Ax connection in the code activity). Then an activity will solicit the user’s company by uttering:

 

“Enter your customer I.D, either by using the phone keypad or by saying your customer number or by saying your company name.”

 

The user has some choices here: Either the user can use the phone’s keypad to press the customer number (like 4-0-0-1), the user can say the digits (“four”, “zero”, “zero”, “one”) or the number (“four thousand and one”) or the user can say the name of the company (“The Glass Bulb”). The functionality of the Question/Answer activity is such that the question soliciting the company will be repeated until an answer is provided. When a number is entered by any of the means discussed above, there is a validation that the number denotes a company in Ax. For convenience, the initialization phase of the workflow gets all the customers into an XML document (through the business connector); The validation described above consists of an XPath query over this document. In this way the chattiness between the workflow and Ax is reduced.  If the validation fails, the user is informed that the input did not designate a valid customer and he is asked for the ID again through the execution of the Goto activity.

 

At this time, the user has supplied a valid company. We now need to get the items that the user is ordering. This is done in a two phase process: The first step is to enter an item by uttering:

 

“Enter an order item. You may say either the item number or the item name.”

 

This is very similar to what was done above to get the customer id. In this case, however, the items ids do not lend themselves easily to entry through the keypad. The user may enter an item id, like “CN-01” or an item name like “Chrome Architect Lamp”. Once the item has been entered and validated (again by consulting an XML document that was fetched from Ax up front using the business connector), the user us asked for the quantity that he wants to order:

 

“How many items do you wish to order? Use the keypad or say the number.”

 

The user can say “Three” or use the 3 key on the phone to achieve the same result. At this point the user will be notified of the item and how many were ordered:

 

“You added three CN-01 to your cart”

 

The information about the items ordered are stored in a list ready for later use. At this point the process of identifying an item and how many restarts. To be able to exit this loop, there is the option of pressing the hash key (#) on the keypad or saying “stop” where an item is expected. When this is done, execution picks up at the code activity that adds the sales order to Ax, again using the business connector. The user is they told that the order will be shipped to the address that the system has on file for the customer.

 

The demo as presented to you here was written in a hurry, mainly in a hotel room with jetlag, as is the norm before conferences. There is one thing that I would have liked to invest more in that would have made the solution more suitable for production, but wasn’t a concern In a tightly controlled demo situation. This has to do with how the  grammars to recognize the user’s utterances are created. Currently the grammars are built from the contents of the Ax database (through the XML files that I alluded to above). While this does work in a demo, it is an inflexible solution because it only allows the user to say exactly what is expected as in:

 

“chrome architect lamp”.

 

What is needed is a conversational grammar so the user would be able to say:

 

“I’d like a chrome architect lamp”,

                                         

or

 

“Gimme a chrome architect lamp please”

 

The Speech Server SDK does provide for such grammars, but I did not have the time to create the conversational grammars that are needed for this, most notably time to figure out how to use the editor used for creating these grammars. In that tool you can define all sentences that a caller can use. I hope to learn that in a future project, so Speech Server can understand flexible spoken input based on content from a database with natural language sentences. For that tool seems really powerful!

 

 

Pen based form demo

This demo was written for Convergence 2007 to showcase how Dynamics Ax can leverage managed code. In this example it uses a managed API to allow form navigation using tablet gestures, that is small figures drawn with the pen on the tablet screen.

 

The technology shown here will work in 4.0 as well as in 5.0 when that version hits the streets.

 

The theory of operation of this is quite simple, because of the beautiful API that is provided for capturing gestures. This application is merely scratching the surface of what this API can do. The only thing needed for this to work is to set up an ink analyser to do the heavy lifting for you. For this example we choose to handle the following gestures:

 

Stroke Action
Left Previous record
Right Next record
< First record
> Next record
Check Close form
Scratch out Delete this record
Circle  Print this record

The complete list of available gestures can be found at http://msdn2.microsoft.com/en-us/library/ms827547.aspx

The trick for this is how to communicate the event that happens in the managed world to the unsuspecting X++ code. There is no direct way of subscribing to an event in the managed world in X++. The way I have done this may be useful in other scenarios as well, so I'll describe it in some detail:

The control on which the gestures are drawn is a Window control, which has no other semantics than publishing its window handle. As anyone who has done any windows programming back in the old unmanaged days will know, the windows handle is an identifier that is used to identify the window for all operations (drawing etc) that takes place in the window. The trick here is to pass this windows handle to the constructor of the class that recognizes the gestures. When a gesture is recognized, the proper event handler is called, and this event handler will simply post a custom message to the windows handle. When Ax is done with its own messages, this message will be handled. There is no default handler for this message (it is a custom message after all, not a predefined one), but because Uffe had a remarkable foresight 10 years ago, he added a way of subscribing to these custom events.This is done by calling the installMessageProc method on the form with the name of a method to call as a result of the given message being posted to the given handle. The init method on the form then does the following:

void init()
{
   int h;
   // ... 
   super();
   
   h = PenArea.hWnd(); // Get the windows handle from control.

   // Pass it on the the stroke handler so it can react by  
   // posting messages to this windows handle:
   strokeHandler = new Villadsen.StrokeHandler(h);

   // Make sure the method called CallbackMethod is called  
   // when the custom message with the id 0x7ffe is posted 
   // to the handle
   this.installMessageProc(0x7ffe, h, "Callbackmethod");

   // ...
}

The CallbackMethod then simply branches on the gesture that was recognized and does the appropriate thing. For legibility I have created a method for each action.

void CallbackMethod(int hwnd, int message, int wParam, int lParam, int px, int py)
{
   #InkGestures

//    print "Called back! Value is ", wparam, ",", lparam;

   switch (lparam)
   {
       case #Left:
           Element.DoPrevious();
           break;
       case #Right:
           Element.DoNext();
           break;
       case #ChevronLeft:
           Element.DoFirst();
           break;
       case #ChevronRight:
           Element.DoLast();
           break;
       case #Check:
           Element.close();
           break;
       case #ScratchOut:
           Element.DoDelete();
           break;
       case #Circle:
           // Circle has to be drawn pretty fast.
           Element.printPreview();
           break;
   }
}

 

The managed code (i.e the StrokeHandler) is shown below. Note that the managed libraries do not have an implementation of PostMessage, so one is created by using the DllImport facility.

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using Microsoft.Ink;
using System.Runtime.InteropServices;

namespace Villadsen
{
   public class StrokeHandler
   {
       private Microsoft.Ink.InkOverlay inkOverlay;
       private Microsoft.Ink.InkAnalyzer inkAnalyzer;
       private int handle;

       [DllImport("user32", EntryPoint = "PostMessage")]
       public static extern int PostMessageA(int hwnd, int wMsg, int wParam, int lParam);

       public StrokeHandler(int windowsHandle)
       {
           this.handle = windowsHandle;

           this.inkOverlay = new InkOverlay((IntPtr)this.handle);
           this.inkAnalyzer = new InkAnalyzer(inkOverlay.Ink, null);

           // Only collect gestures
           inkOverlay.CollectionMode = CollectionMode.GestureOnly;
           // But do collect all of them.
           inkOverlay.SetGestureStatus(ApplicationGesture.AllGestures, true);
           // Call this event handler to handle an incoming gesture
           inkOverlay.Gesture += new InkCollectorGestureEventHandler(inkOverlay_Gesture);
           // The drawing surface should be in front of the control.
           inkOverlay.AttachMode = InkOverlayAttachMode.InFront;
           // Draw in red
           inkOverlay.DefaultDrawingAttributes.Color = Color.Red;

           inkOverlay.Enabled = true;
       }

       // Event handler for the incoming events. This will post the incoming gensture
       // back to Ax through the windows handle of the form that created the StrokeHandler.
       void inkOverlay_Gesture(object sender, InkCollectorGestureEventArgs e)
       {
           // System.Console.WriteLine("Got a gesture {0}", e.Gestures[0].Id);
           PostMessageA(this.handle, 0x7ffe, 1, (int)e.Gestures[0].Id);
           e.Strokes.Clear();
       }
   }
}

That's it! That's really all there is to it.You can find the source code on ftp://ftp.villadsen.dk/GestureDemo.zip

 

 

Difference between " and ' ?

We have been asked whether there is any difference between the two types of quotes that can be used by Ax. The answer is very simple: X++ makes absolutely no distinction between the two. The reason that there are two quote characters is to facilitate constructs themselves containing quote characters, like

str s = ‘<MyTag attr="hello">’;

Writing this with only one quote character would be difficult to read because escape characters would have to be used to distinguish the quotes defining the extent of the string from those that occur in the string, as

str s = "<MyTag attr=\"hello\">";

That said, the application community has best practices that mandate that programmers use '(single quotes) for text that cannot appear on UI, like

System.Int32 myVar = CLRInterop::CLRNull('System.Int32');

Whilst text that does should use double quotes for text like

myButton.Label = "@SYS12345";

 

SQL improvements in the next version of Dynamics AX

I'd like to share with you a description of some of the features that we've finished for the next release. The features described in this blog post are improvements and additions to the X++ data access statements. One deals with ordering and grouping data, another expands the possibilities of the update statement by allowing join clauses; The third feature involves allowing values (or more precisely, constants that are not field related) into the selection that is inserted into a table in an insert_recordset statement.

Ordering and grouping Data

Previously, the order by and group by clauses acted on the table in the current join or select clause. So, the following

1: CustTable ct; 
2: CustTrans transactions; 
3:  
4:  select  *  from ct  order by AccountNum  
5:  join transactions  order by AmountCur  
6:  where ct.AccountNum == transactions.AccountNum;

would order by the account number, then on the amount. Note that the order by syntax doesn't include the table to which the field belongs, because it is implicit in the order by clauses position. This way of doing things works fine so long as you do not have requirements to order in a different order than what is mandated by the join structure. For instance, if you wanted to order by amount and then by accountnum you'd have to reverse the select/join hierarchy. To get rid of these problems, we have introduced a way of specifying an order by (and a group by) list that is not tied to a particular place in the join hierarchy. This is done by specifying a list of qualified names of fields in a single order by or group by clause:

1:  select *  from ct  
2:  join transactions  
3:  order by transactions.AmountCur, ct.AccountNum  
4:  where ct.AccountNum == transactions.AccountNum;

There can be only one such augmented order by clause in a selection.

Improvements to the update statement

The update statement may now have a join clause attached to it, as shown in the code snippet below:

1: // Three Table Join 
2: update_recordset salestable 
3: setting currencycode = 'USD' 
4: join custtable 
5: where  custtable.accountnum = salesTable.custaccount 
6: join custgroup 
7: where custgroup.name == 'Retail' && custgroup.custgroup == custTable.custgroup;

We have also added some diagnostics that will cause the compiler to complain if the user tries to use the value of fields coming from tables that are given in exists and non exists joins (The database backend is not required to fetch values for these, merely to find out if there are matching records or not).

The Insert statement.

The insert statement is generally used to insert data from one table into another. The programmer typically writes a selection of fields matching the fields to insert into the target table and delimits the data by using suitable where and join clauses:

1: insert_recordset MyTargetTable(targetField1, targetField2) 
2:     select sourceField1, sourceField2 from MySourceTable where ... 
3:  

This is indeed suitable for the case where all the data to be inserted into the target table is already stored in the source table, but that is not always the case. The new feature allows the user to insert values that are given by variable references, not field references.

Exception handling and transactions

The runtime semantics of exceptions is peculiar in X++. The difference from the semantics from other languages is that exception handling considers any catch blocks that are in a scope that contains a running transaction as ineligible to handle the exception. Consequently the behavior of exceptions is quite different in the situation where a transaction is running and in the situation where no transactions are running. This makes the exception semantics different from exception semantics of other languages, even though the syntax is very much the same. This is a perfect setup for confusion even for seasoned X++ programmers.

Please consider the example below for the following discussion:

 1: try 
 2: { 
 3:     MyTableType t; 
 4:     ttsBegin;    // Start a transaction 
 5:     try  
 6:     { 
 7:         throw error(“Something bad happened”); 
 8:     } 
 9:     catch 
10:     { 
11:         // Innermost catch block 
12:     } 
13:     update_recordset t … // Must run within a transaction 
14:     ttsCommit;    // Commit the transaction 
15: } 
16: catch 
17: { 
18:     Info (“In outermost exception handler”); 
19: } 
20:  
20: 

When the exception is thrown in the code above, the control is not transferred to the innermost catch block, as would have been the case if there had been no transaction active; Instead the execution resumes at the outermost exception handler, i.e. the first exception handler outside the try block where where the transaction started (This is the catch all exception handler in line 19).

This behavior is by design. Whenever an exception is thrown all transactions are rolled back (except for two special exceptions namely Exception::UpdateConflict and Exception::UpdateConflictNotRecovered, that do not roll back the active transactions). This is an inseparable part of the semantics of throwing exceptions in X++. If the exception is not one of the two exceptions mentioned above, the flow of control will be transferred to the handler of the outermost block that contains a TTSBegin. The reasoning behind this is that the state of the database queries would not be consistent when the transactions are rolled back, so all code potentially containing this code is skipped.

Another issue is balancing of the transaction-level.

Please consider an amended example:

 1: try 
 2: { 
 3:     MyTableType t; 
 4:     ttsBegin;      // Start a transaction – Increment TTS-level to 1 
 5:     try  
 6:     { 
 7:         ttsBegin;  // Start a nested transaction – Increment TTS-level to 2 
 8:         throw new SysException(“Something bad happened”); 
 9:         ttsCommit;     // Decrease TTS-level to 1 
10:     } 
11:     catch 
12:     { 
13:         // Innermost catch block 
14:         // What is the tts level here? 
15:         info (appl.ttslevel()); 
16:         // Something bad happened -> abort the transaction 
17:         ttsAbort; 
18:     } 
19:     update_recordset t … // Must run within a transaction 
20:     ttsCommit;       // Decrease TTS-level to 0, and commit 
21: } 
22: catch 
23: { 
24:     Info (“In outermost exception handler”); 
25: } 

Let’s examine the case where we allowed the inner catch block to run as the result of throwing the exception. The question is: What should the TTS-Level be in the innermost catch block?

TTSlevel is 1: This won’t work, as the innermost catch block must be able to compensate (and commit) or abort the level-2 transaction. TTS Level 1 would mandate that the system has taken the decision to either abort or commit the level-2 transaction.

TTSlevel is 2: This won’t work either, as the innermost catch block should be able to abort the level-2 transaction. A ttsabort inside the innermost catch block will also abort the level-1 transaction, and render the code following the catch block void. This in turn requires that the innermost catch block throws a new exception, and then we end up in the outermost catch block anyway.

I hope this provides a little clarity in these murky waters.
 

More Posts Next page »
Page view tracker