Throughout most of this document, we have assumed that all code works without errors, because we did not want error handling issues to complicate the discussion. But in reality, almost all code could potentially run into
problems of one sort or another, so proper error handling and recovery is very important. Without it, there is some danger that NX will be left in an unpredictable state.
■ Exceptions
When some piece of code encounters a situation that it cannot handle, it must signal this somehow. In modern VB code, an error condition is indicated via an “exception”. We say that the problematic code “raises” or “throws” an exception. Some examples of situations that might cause this to happen are:
Trying to perform some operation on an object that is Nothing
Trying to divide by zero
Trying to access an array element that is beyond the bounds of the array
Trying to access a file that doesn’t exist
Trying to create an NX circle with zero radius
In order for the program to continue, the exception must be handled by the function that encountered it, by the function that calls this function, or by some other higher level function. The exception is passed up the “call stack”
from called function to calling function until it is handled. If the exception is not handled anywhere in the call stack, the program will terminate.
The code to handle an exception has the following basic structure:
Try
'Some code that might encounter a problem Catch ex As System.Exception
' Code to react to the problem End Try
So, the code that might encounter a problem is placed in a “Try” block. If a problem arises, an exception is raised, and control is transferred immediately to the “Catch” block, where we insert some code to react to the problem. The exception object is available in the variable named in the Catch statement (the variable “ex” in the example above), so the code within the Catch block can use it.
Your code might include several Catch blocks, each handling exceptions of a specific type. The system examines these Catch blocks in order, looking for one that handles the type of exception that arose. Here are some examples of common types of exceptions, corresponding to the problems listed above:
Exception Type Thrown when you try to …
System.NullReferenceException Perform some operation on an object that is Nothing System.DivideByZeroException Divide by zero (with integer variables, anyway)
System.IndexOutOfRangeException Access an array element that is beyond the bounds of the array System.IO.FileNotFoundException Access a file that doesn’t exist
System.StackOverflowException The system runs out of stack space
NXOpen.NXException Create an NX circle with zero radius (or thousands of other situations) This method of dealing with errors is called “structured exception handling”, and it is widely used in modern VB programs, and also in other programming languages, so you can easily find tutorial materials discussing it.
■ Example: Unhandled Exceptions
Let’s see what happens if our code raises an exception, and we do not handle it. Specifically, let’s run the following (ridiculous) code in a few different ways:
Module MyCode Public Sub Main()
Dim s1 As String = "hello"
Dim x1 As Double = Double.Parse(s1) End Sub
End Module
This code tries to parse a given string and convert it to a Double. This will work fine with a string like “3.14”, but it obviously won’t work with the string “hello”. The example is rather silly, and we can immediately see what the problem is. However, a very similar situation arises if we ask the user to type in a number — there is nothing to stop him (or her) typing “hello”, instead of a number, so parsing errors of this type are quite common. If we run this code from the command prompt, here is what happens:
As you can see, the System.Number.ParseDouble function raises a System.FormatException, complaining that the input string was not in a correct format. The exception is not handled, so it is passed up the call stack to the System.Double.Parse function, which again does not handle it. Eventually, the exception reaches our MyCode.Main function, where it again goes unhandled, so our program crashes.
The situation is slightly better if we run this code from inside NX using File→Execute. We get the following error:
If we look in the system log, we see the following:
Caught exception while running: Main
System.FormatException: Input string was not in a correct format.
at System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt) at System.Double.Parse(String s)
at ExceptionSample.MyCode.Main()
This is almost exactly the same sequence of error messages that we saw before. The only difference is that the first line now says that the exception was caught, and did not go unhandled. If we run the same code in the NX Journal Editor, we get a slightly more helpful error message that tells us in which line of code the error occurred:
NX provides a high-level mechanism that catches any exception thrown by code run in the Journal Editor or via FileExecute. So, in both cases, the System.FormatException was caught by code inside NX, and this prevented NX from crashing.
■ Handling an Exception
Next, let’s modify our flawed code, and handle the System.FormatException ourselves, so that it does not “bubble up” to the high-level exception handling mechanism inside NX. Here is the revised version:
Module MyCode Public Sub Main()
Dim s1 As String = "hello"
Try
Dim x1 As Double = Double.Parse(s1) Catch ex As System.FormatException Guide.InfoWriteLine(ex.Message)
Guide.InfoWriteLine ("Idiot. That string isn't a number.") End Try
End Sub End Module
This code runs without any visible errors, and we get the following output in the NX Info window:
The erroneous call to Double.Parse is inside a Try block, so the exception is caught, control passes to our Catch block, and two lines of text are written out to the Info window. The first line is the text from the Message property of the exception, and the second line provides some further information about what (probably) went wrong.
■ Exception Properties
In the code above, we made use of the Message property of an Exception. There are some other properties that are also useful, sometimes:
Property Description
Message The error message associated with this exception.
InnerException The Exception instance that caused the current exception.
Source The name of the application or the object that caused the error.
StackTrace A string representation of the call stack at the time the exception was thrown.
TargetSite The method that threw the current exception.
ToString Returns a string representation of the exception
In practice, the ToString function is often the most useful, since it returns a combination of the Message and StackTrace properties. In the case of the FormatException we have been working with, the ToString function gives:
System.FormatException: Input string was not in a correct format.
at System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt) at System.Double.Parse(String s)
at MyCode.Main() in C:\Temp\NXJournals5384\journal.vb:line 8
We have seen this sort of text before in various error messages, of course — it appears that those error messages might have been constructed just by using the output from the ToString function.
■ NX Exceptions
The exceptions thrown by NX are all of type NXOpen.NXException, which is derived (indirectly) from
System.Exception. In addition to the general properties of System.Exception listed above, an NXOpen.NXException has a useful property called ErrorCode, which allows us to distinguish one type of error from another. Typically, your code will test the value of the ErrorCode property, and branch accordingly. Here is an example that deals with some problems that might arise when creating a circular arc:
Dim radius = 1.0 ' Radius
Dim angle0 = 0.0 ' Start angle (in radians) Dim angle1 = 1.0 ' End angle (in radians) Try
workPart.Curves.CreateArc(center, axisX, axisY, radius, angle0, angle1) Catch ex As NXOpen.NXException
If ex.ErrorCode = 1710021 Then
Guide.InfoWriteLine("Radius must be at least 1e-9.") Guide.InfoWriteLine(ex.ToString)
ElseIf ex.ErrorCode = 1710014 Then
Guide.InfoWriteLine("Angular span must be at least 1e-11 radians.") Guide.InfoWriteLine(ex.ToString)
Else
Guide.InfoWriteLine("Unknown problem in creating arc.") Guide.InfoWriteLine(ex.ToString)
End If End Try
If we run this code with radius = 0, we get the following output:
Radius must be at least 1e-9.
NXOpen.NXException: Invalid Arc Radius.
at NXOpen.CurveCollection.CreateArc(Point3d center, Vector3d xDirection, ...) at ExceptionSample.MyCode.Main() in C:\ExceptionSample\MyCode.vb:line 15
and if we run it with angle1 = 0, we get
Angular span must be at least 1e-11 radians.
NXOpen.NXException: Illegal Arc Length Specified.
at NXOpen.CurveCollection.CreateArc(Point3d center, Vector3d xDirection, ...) at ExceptionSample.MyCode.Main() in C:\ExceptionSample\MyCode.vb:line 15
By testing the value of the ErrorCode, we can determine what went wrong and provide error messages that are a bit more helpful than “Invalid Arc Radius” or “Illegal Arc Length”.
For a given NX Open function, there is unfortunately no documentation that indicates what values of ErrorCode it might return, so you have to discover these by trial and error.
■ Using Undo for Error Recovery
In the examples above, we have merely trapped exceptions and reported them. But often this is not enough — we may need to perform some recovery operations to ensure that NX has been returned to a safe and predictable state.
The Undo methods in NX Open provide an easy way to do this. Before attempting a risky operation, your code should create an Undo Mark, which will save the current state of NX. If your program encounters an error and needs to recover, you can “roll back” and return NX to this safe saved state. The general approach is as follows:
' Create an invisible Undo mark Dim myMarkName As String = "beginning"
Dim myMark = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, myMarkName) Try
' Try something risky (more risky than just creating a sphere, typically) Guide.CreateSphere(3,0,0, 1)
In many cases, it’s possible to avoid exceptions. For example, you can often test input data before passing it to a function that might have trouble with certain values. This might improve performance slightly if many exceptions are involved, because raising exceptions is time-consuming. More importantly, removing Try/Catch blocks sometimes makes your code easier to read. In the arc creation shown in the code above, we could have easily avoided the two specific exceptions by writing:
If r < 1e-9 Then r = 1e-9 ' Radius must be at least 1e-9 If a1 < 1e-11 Then a1 = 1e-11 ' Angular span must be at least 1e-11 workPart.Curves.CreateArc(center, axisX, axisY, r, a0, a1)
Of course, the Try/Catch block will still be needed unless you can anticipate all conceivable problems that might arise when calling the CreateArc function.
In some cases, the .NET framework provides functions that are specifically designed to help you avoid exceptions.
Failure of the Parse function when converting a string to a number (as in our earlier example) is very common, so there is a special TryParse function that will not throw an exception if it fails. Similarly, there is a TryCast function that you can use instead of CType. If the cast fails, TryCast returns Nothing, rather than raising an exception.
However, there are certain exceptions are simply unavoidable. For example, when you try to open a file, it may happen that the file does not exist, in which case a FileNotFound exception will be raised. You could test to see if the file exists before trying to open it, but even this is not fool-proof – there is some (very small) chance that the file was deleted after you tested but before you opened it.
■ The Finally Block
The full form of the Try/Catch construct also includes a “Finally” block, like this:
Try
'Some code that might encounter a problem Catch ex As Exception
' Code to react to the problem Finally
' Cleanup code that must be executed End Try
The code in the Finally block is guaranteed to be executed, unless there is some disaster like a stack overflow or someone unplugging your computer. Specifically, it will be executed even if there is an exception or a Return statement in the Catch block. So, the Finally block is a good place to put cleanup code that must be run to free resources. An example is the code that can be found in the typical Main function that displays a block-based dialog:
Public Shared Sub Main() Try
' Try to create and display a WidgetDialog theWidgetDialog = New WidgetDialog
theWidgetDialog.Show Catch ex As Exception
' If an exception was raised, display an error message Dim theUI = NXOpen.UI.GetUI
Dim errorType = NXOpen.NXMessageBox.DialogType.Error
theUI.NXMessageBox.Show("WidgetDialog error", errorType, ex.ToString) Finally
' Regardless of what happened, free the resources used by the dialog theWidgetDialog.Dispose
End Try End Sub End Sub
The call to the Dispose function is needed to ensure that resources used by theWidgetDialog are correctly released.
By placing this call in the Finally block, we are ensuring that it will be executed regardless of whether an exception occurred or not.