Debug .NET NX Journals

Table of Contents

Background

Journals1 are a popular means of automating and customizing processes in NX. No compiling and no additional licenses are required for execution. But there are also several disadvantages. One that is mentioned sometimes, is that debugging is not possible. However, this is not true. With a small detour, C # and VB.NET journals can be debugged with Visual Studio or WinDbg. This effort can be worthwhile for larger journals or persistent bugs.

Approach

The way NX .NET runs journals is critical to solving the problem. The journal to be executed is first copied into a temporary directory under a generic name. This copy is compiled at runtime and then deleted. Now NX loads the resulting .NET assembly like a regular NX Open program. After executing the program, NX deletes the temporary directory and its contents. During the execution of the program, almost everything necessary for debugging is available in the temporary directory with the assembly and the associated symbol file (pdb file). Only the source code referred to in the symbol file is no longer available at runtime.
Assuming that the original journal from NX is copied unchanged into the temporary directory, the source code can be restored from the original file. The name of the source code file must match the name of the assembly. Additionally, the copy may only be made when the program is already being processed. Now the source code file can be opened in Visual Studio or another debugger and the debugger can be connected to the NX process.
A quick test shows that this approach works if the original journal has line breaks in Unix format (LF). With line breaks in Windows format (CR LF), copying and renaming the journal is not sufficient. In this case, the MD5 hash that is stored for the source code in the assembly does not match that of the copied file. Adjusting the line breaks in the copied file is necessary.

Implementation

Some steps necessary for debugging can be done by the journal itself. These are summarized here in a small method that can be called immediately after the start of the journal.

/// <summary>
/// Prepare debugging of uncompiled journals.
/// </summary>
public static void InitJournalDebug()
{
    string journalPath = Session.GetSession().ExecutingJournal;
    string extension = Path.GetExtension(journalPath);
    Uri uri = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase);
    string tmpSourcePath = Path.ChangeExtension(uri.LocalPath, extension);
    using (StreamWriter sw = File.CreateText(tmpSourcePath))
    {
        sw.NewLine = "\n";
        foreach (string line in File.ReadLines(journalPath))
        {
            sw.WriteLine(line);
        }
    }
    UI.GetUI().NXMessageBox.Show("Source File", NXMessageBox.DialogType.Information, string.Format("Source file for debugging:\n{0}", tmpSourcePath));
}

The method copies the original journal provides it with the correct line breaks and renames it. Then the program execution is stopped using a dialog. The source code file displayed in the dialog can be opened by the user in Visual Studio and provided with breakpoints as required. Finally, the debugger must be attached to the NX process (ugraf.exe), and the program continued by confirming the dialog.

A minimal journal could look like this:

using NXOpen;
using System;
using System.IO;
using System.Reflection;

public class Journal
{
    /// <summary>
    /// Journal entry point.
    /// </summary>
    /// <param name="args"></param>
    /// <returns></returns>
    public static int Main(string[] args)
    {
        int retValue = 0;
        try
        {
            InitJournalDebug();//Only for debugging, remove in production

            //Do something.
            double result = Foobar(Math.PI, Math.E);
        }
        catch (NXOpen.NXException e)
        {
            UI.GetUI().NXMessageBox.Show("Message", NXMessageBox.DialogType.Error, e.Message);
        }
        return retValue;
    }

    /// <summary>
    /// Prepare debugging of uncompiled journals.
    /// </summary>
    public static void InitJournalDebug()
    {
        string journalPath = Session.GetSession().ExecutingJournal;
        string extension = Path.GetExtension(journalPath);
        Uri uri = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase);
        string tmpSourcePath = Path.ChangeExtension(uri.LocalPath, extension);
        using (StreamWriter sw = File.CreateText(tmpSourcePath))
        {
            sw.NewLine = "\n";
            foreach (string line in File.ReadLines(journalPath))
            {
                sw.WriteLine(line);
            }
        }
        UI.GetUI().NXMessageBox.Show("Source File", NXMessageBox.DialogType.Information, string.Format("Source file for debugging:\n{0}", tmpSourcePath));
    }

    /// <summary>
    /// Do something.
    /// </summary>
    /// <param name="foo"></param>
    /// <param name="bar"></param>
    /// <returns></returns>
    public static double Foobar(double foo, double bar)
    {
        double res = foo * bar;
        return res;
    }

    /// <summary>
    /// Unload option.
    /// Without effect for uncompiled journals
    /// </summary>
    /// <param name="arg"></param>
    /// <returns></returns>
    public static int GetUnloadOption(string arg)
    {
        return System.Convert.ToInt32(Session.LibraryUnloadOption.Immediately);
    }
}
NX Dialog showing the path to the source code.
Attach the debugger.

Conclusion

It is possible to use a debugger to analyze errors in uncompiled journals. It’s a little cumbersome, but it expands the possible uses of these journals since it enables advanced troubleshooting in more complex programs.


  1. The term journal is often used in a broader sense for NX Open programs in general. Here, however, it is used only for programs implemented in VB.NET or C # that are executed directly from source code. ↩︎