Foxpro try catch получить текст ошибки

« Назад

Команда

TRY
                   [tryCommands]
         [CATCH [TO VarName] [WHEN lExpression]
                   [catchCommands]]
         [FINALLY
                   [finallyCommands]]
         ENDTRY

употребляется для обработки ошибок исполнения (исключений).

Команда является конструкцией. Порядок ее выполнения следующий:

— первоначально выполняются операторы tryCommands;

— если при выполнении tryCommands ошибки не возникло, то управление передается FINALLY-блоку;

— если при выполнении tryCommands произошла ошибка, то VFP создает объект-исключение и передает управление CATCH-блоку; ссылка на объект-исключение сохраняется в переменной VarName. После обработки ошибки управление будет передано FINALLY-блоку.

Команда ENDTRY, завершающая конструкцию, обязательна. Ее отсутствие может привести к зависанию приложения (VFP перестанет отвечать).

Параметры:

tryCommands – операторы TRY-блока, которые могут вызвать ошибку.

VarName – переменная, хранящая ссылку на объект-исключение.

Задание CATCH без параметров аналогично заданию

CATCH WHEN .T.

Если задана опция WHEN и lExpression вычисляется со значением .F., то VFP освобождает объект-исключение и устанавливает в VarName NULL. Также объект-исключение освобождается, когда переменная VarName, содержащая ссылку на этот объект, освобождается или изменяет значение.

Переменная VarName не может быть ссылкой на свойство объекта.

lExpression – логическое выражение; операторы catchCommands выполняются, если lExpression вычисляется со значением .T., и не выполняются – в противном случае. Выражение lExpression может содержать ссылку на объект-исключение (посредством VarName), анализ значения которой позволяет определиться в отношении мер по обработке ошибки.

catchCommands – операторы CATCH-блока, обрабатывающие ошибку.

finallyCommands – операторы FINALLY-блока, выполняемые после операторов tryCommands, если последние не вызвали ошибки, или после операторов catchCommands, если ошибка обнаружена. FINALLY-блок может содержать операторы, освобождающие выделенные в TRY-блоке ресурсы.

Пример:

&& Ошибку обработает CATCH-блок

try

&& Попытка извлечь корень из отрицательного числа приведет к ошибке

x = Sqrt(–5)

catch to oErr

set space off

&& oErr – идентификатор объекта-исключения

? «Catch:», oErr.ErrorNo, «; «, oErr.Message

store .NULL. to x

&& Управление передается FINALLY-блоку

finally

if not IsNull(x) then

? x

else

  ? «Результат не найден!»

endif

endTry

Сообщения программы:

Catch: 61; SQRT( ) argument cannot be negative

Результат не найден!

With the introduction of Visual FoxPro 3.0, error handling in VFP changed substantially.

Rather than using «on error» statements, «state of the art» error events became available. Now, 7 years later, more sophisticated error handling mechanisms take center stage as Visual FoxPro 8.0 introduces structured error handling.

Handling potential errors in the most graceful way has been a goal of software developers since the very early days of programming, and the quest for the perfect methodology is still ongoing. FoxPro and Visual FoxPro have gone through a number of different ways to handle errors (all of which are still available today and are useful for different scenarios).

The «most traditional» way to handle errors in FoxPro (and even in FoxBase, before) was the ON ERROR statement. This command tells FoxPro what to do in case of an error. Arguably one of the most common scenarios would be to call a procedure that handles an error in the following fashion:

ON ERROR DO ErrorHandler

Each Try-block needs to have at least a CATCH or a FINALLY block.

Or, you might use a slightly more sophisticated version, as suggested by the Visual FoxPro documentation:

ON ERROR DO errhand WITH ;
   ERROR( ), MESSAGE( ), MESSAGE(1),;
   PROGRAM( ), LINENO( )

Of course, in the object-oriented world that Visual FoxPro lives in, this is a very procedural way to handle things. Luckily, the ON ERROR command can evaluate any Visual FoxPro expression, including calling methods on an object:

ON ERROR oErrorHandler.Handle()

This approach works rather well in scenarios where a global error handler is used. However, this type of error handler is generally not used in an object-oriented environment. There are a number of reasons for this. First of all, in order to create black-box objects, those objects have to handle their own errors to conform to the rules of object-oriented development. However, to make those objects handle their own errors, we would have to set up an error handler like so:

ON ERROR THIS.HandleErrors()

Unfortunately, this doesn’t work, because whatever is specified as the ON ERROR statement will run as if it were a separate procedure. In other words, this line of code will run outside the object. Therefore, the THIS pointer is not valid.

Another issue is that the ON ERROR statement would not be scoped to the object. Consider the following example:

ON ERROR * && Ignore errors
LOCAL loExample
loExample = CREATEOBJECT("Example")
xxxxxxx && Syntax error
RETURN

DEFINE CLASS Example AS Custom
   FUNCTION Init
      ON ERROR THIS.HandleErrors
      RETURN .T.
   ENDFUNC
   
   FUNCTION HandleErrors
      MESSAGEBOX("Handling Errors...")
   ENDFUNC
ENDDEFINE

In this particular example, the first line instructs VFP to ignore all errors (the error statement is an asterisk, which is a comment line). Then, the Example object gets instantiated, and its constructor (Init()), sets the error statement to «THIS.HandleErrors» (for now, let’s just assume that would be a valid line of code). After the object is instantiated, a line of code with a syntax error («xxxxxxx») executes, raising an error.

The question is: What error handler will handle that error? Since we now know that ON ERROR is not scoped to objects, we also know that the error is handled by «THIS.HandleErrors». Clearly, even if that would call a method on the right object, this wouldn’t be desired, since the object has no knowledge about how to handle errors that may occur outside the object. Similarly, error handlers defined after the object is instantiated would throw off error handling within the object. Neither scenario will allow us to create black box objects.

One possible solution would be to create an object devoted to error handling. This object could be created when the main object gets instantiated. However, to make this work, the new object would have to be referenced through a public variable so it could be referenced (again, THIS.oError.HandleErrors() would not work). This could lead to collisions with other objects that employ the same approach. Also, each individual method would have to set the error handler to that handler object, and reset it back not only when the method completed, but also every time the object called out to other code (which may or may not use a similar approach). This certainly would be an error-prone solution. Let’s not even investigate it any more, although I could point out a long list of other problems.

Clearly, a better way to handle errors was required. For this reason, Visual FoxPro 3.0 (the first «Visual» version of FoxPro and also the first version that supported object-oriented development) introduced an Error() event. Using that mechanism, errors could be handled in the following fashion:

ON ERROR * && Ignore errors
LOCAL loExample
loExample = CREATEOBJECT("Example2")
xxxxxxx && Syntax error
RETURN

DEFINE CLASS Example2 AS CUSTOM
   PROCEDURE INIT
      xxxxx && Syntax error
   ENDPROC

   PROCEDURE ERROR(nError, cMethod, nLine)
      MESSAGEBOX("Error inside the object")
   ENDPROC
ENDDEFINE

The idea here is simple: Whenever an error occurs anywhere within the object, the Error() event will fire. If the error occurred anywhere outside the object, it will be handled by whatever error handler is defined there. In our example, the syntax error in the Init() method will be handled by the Error() method, and the syntax error in the line of code after the CreateObject() will be handled by the ON ERROR error handler (which will actually hide the error).

This mechanism has a number of advantages. First of all, it allows building self-contained objects. Secondly, it splits the gigantic task of handling errors globally, into smaller, more digestible pieces. No longer are we dealing with handling a very large number of errors. For instance, if the object at hand doesn’t deal with database tables, we probably don’t have to worry about handling any database errors.

However, this approach also has some problems. For example, it still may be handling errors on a scale much larger than we want. Objects can be large and do a large number of different things, each of which may have only a very limited number of scenarios that may go wrong. In total, however, the object might require a very complex error handler.

Another problem is that this type of error handler makes it very difficult to «exit gracefully» whenever an error has occurred. Consider the following example:

DEFINE CLASS WordExport AS Custom
   FUNCTION Export(lcText1,lcText2)
      LOCAL oWord as Word.Application
      oWord = CREATEOBJECT("Word.Application")
      oWord.Application.Visible = .T.
      oWord.Documents.Add()
      oWord.Selection.InsertAfter(lcText1)
      oWord.Selection.InsertAfter(lcText2)
      RETURN .T.
   ENDFUNC

   PROCEDURE ERROR(nError, cMethod, nLine)
      MESSAGEBOX("Error exporting to Word!")
   ENDPROC
ENDDEFINE

The idea behind this simplified example is that the WordExport object can be used to create a Word document on the fly. To do so, the developer simply instantiates this object and passes some text to the Export() method. The method then opens an instance of Word, makes it visible, creates a new document and exports the text.

What would happen if the user actually closed Word right after a new document has been created (right after the Documents.Add() line)? Well, the next two lines of code would both cause an error (and so would hundreds of other lines if this was a life-size example).

But what could our error handler do to solve the problem? Well, beyond displaying the error in a message box, the error handler could try to fix the problem. However, this is unlikely in this case, because in order to do that, the method would have to start over from scratch. Since that isn’t something the error handler could do easily, it can choose to ignore the error and proceed with the next line of code, which would then cause another error that could also be ignored, and so forth.

Another option would be to issue a RETRY, which would run the line that failed again, causing another error, which would result in an endless loop if the handler just tried to RETRY again. The only other option we have would be to CANCEL, which would shut down the whole process and not just the current method.

Note also, that the method returns .T., which is the way I would like things to be if the document got created successfully. However, I would like the method to return .F. if there was a problem. This isn’t so easy, since the Error() event doesn’t have any access to the return value of this method.

One possible solution would be a local ON ERROR statement instead of the error method:

DEFINE CLASS WordExport AS Custom
   FUNCTION Export(lcText1,lcText2)
      * We create a new error handler
      LOCAL lError
      lError = .F.
      ON ERROR lError = .T.

      * We run the regular code
      LOCAL oWord as Word.Application
      oWord = CREATEOBJECT("Word.Application")
      oWord.Application.Visible = .T.
      oWord.Documents.Add()
      oWord.Selection.InsertAfter(lcText1)
      oWord.Selection.InsertAfter(lcText2)

      * We reset the error handler,
      * and check if everything went fine
      ON ERROR
      IF lError
         RETURN .F.
      ELSE
         RETURN .T.
      ENDIF
   ENDFUNC
ENDDEFINE

This is an acceptable solution, but there are difficulties with this approach. First of all, the method might call out to other methods that may reset the error handler or point to a different handler. This is a problem that is hard to avoid, since you may not have control over other code that is running.

Also, at a later point in time, someone may want to add an Error() method to this object (perhaps to handle errors that may occur in other methods). The problem with that is that the error method takes precedence over the ON ERROR handler, hence rendering the ON ERROR useless.

Introducing: Try/Catch

To solve these issues, Visual FoxPro 8.0 introduces «Structured Error Handling.» This approach allows the developer to wrap a series of commands into a block that is handled by a local error handler. The advantage of this error handler is that it usually handles a very limited set of potential problems, making it simple and straightforward.

This is the basic syntax for structured error handling in Visual FoxPro:

TRY
   * Do something
CATCH
   * Handle a potential problem
ENDTRY

Let’s see how we could re-work the above example into a scenario handled by a Try/Catch block:

DEFINE CLASS WordExport AS Custom
   FUNCTION Export(lcText1,lcText2)
      LOCAL lReturnValue
      lReturnValue = .T.
      
      TRY

         * We run the regular code
         LOCAL oWord as Word.Application
         oWord = CREATEOBJECT("Word.Application")
         oWord.Application.Visible = .T.
         oWord.Documents.Add()
         oWord.Selection.InsertAfter(lcText1)
         oWord.Selection.InsertAfter(lcText2)

      CATCH
         lReturnValue = .F.
      ENDTRY

      RETURN lReturnValue
   ENDFUNC
ENDDEFINE

As we can see, this is a much simpler way to implement the solution. First of all, it is simply much less «kludgy» and is a very clean implementation. But more importantly, it is a much superior implementation from a technical point of view. The solution is not influenced by outside error handling.

Also, we have full control over what is to happen if an error does occur. Unlike in the example with the error event, we can write code within our method that executes no matter whether an error occurred or not, making it easy to set the return value to our liking. (We were able to do this in the previous example, but the solution was error prone and easy to break by running it in different environments).

Try/Catch blocks can be nested to achieve more granular error handling.

I’m sure that by now you already have a good idea about what Try/Catch does: Whatever code we run inside a Try-block will execute until an error occurs. If an error does in fact occur, the Catch-block is executed. Note that the try block stops executing as soon as an error occurs. There is no way to retry or ignore the error. If that’s what you would like to do, Try/Catch error handling is not the right solution.

Note that the Catch-block is never executed if no error occurs. Sometimes you might want to define code that runs as cleanup code, whether an error occurred or not. Here is an example:

DEFINE CLASS WordExport AS Custom
   FUNCTION Export(lcText1,lcText2)
      LOCAL lReturnValue
      lReturnValue = .T.
      TRY
         * We run the regular code
         LOCAL oWord as Word.Application
         oWord = CREATEOBJECT("Word.Application")
         oWord.Application.Visible = .T.
         oWord.Documents.Add()
         oWord.Selection.InsertAfter(lcText1)
         oWord.Selection.InsertAfter(lcText2)
      CATCH
         lReturnValue = .F.

      FINALLY
         IF VarType(oWord) = "O"
            oWord.Application.Quit()
         ENDIF
      ENDTRY

      RETURN lReturnValue
   ENDFUNC
ENDDEFINE

In this example, we shut down Word, even if something went wrong. Note however, that the error may have occurred before Word ever got instantiated. Therefore we need to first check whether Word is an object. (Actually, things may be a little trickier with automation objects, especially Word, but for simplicity we’ll leave it at that.)

At this point you may wonder why we need a Finally-block. After all, we could have put that code after the ENDTRY and would have achieved an identical result. However, there are scenarios that can greatly benefit from using the finally-block (which we will examine further down), making the use of FINALLY a good idea in general.

One last remark about the basic Try/Catch structure: Each Try-block needs to have at least a CATCH or a FINALLY block. Therefore, you can not just say «try this, and I don’t care of it works or not since I can’t do anything about a potential problem anyway.» If you would like to do that, you can create a Catch-block that has nothing but a comment. A scenario like this may be desired within an error handler:

TRY
   USE Customer
   LOCATE FOR LastName = "Gates"
   IF FOUND()
      StrToFile("Gates found!","customer.log")
   ENDIF
CATCH
   TRY
      StrToFile("Error: "+Message(),"Error.log")
   CATCH
      * Nothing we can do now
   ENDTRY
FINALLY
   IF Used("Customer")
      USE IN Customer
   ENDIF
ENDTRY

This example also demonstrates one of the key features of structured error handling: Nested Try/Catch blocks.

Nested Try/Catch Blocks

Try/Catch blocks can be nested to achieve more granular error handling. There may be a Try/Catch block around the entire application, there may be Try/Catch blocks wrapping entire methods, then there may be individual blocks, and so forth.

Let’s enhance our Word example a little more and instead of creating a blank document, we will create a new one based on a certain template:

FUNCTION Export(lcText1,lcText2)
   LOCAL lReturnValue
   lReturnValue = .T.
   TRY
      * We run the regular code
      LOCAL oWord as Word.Application
      oWord = CREATEOBJECT("Word.Application")
      oWord.Application.Visible = .T.
      TRY
         oWord.Documents.Add("MyTemplate.dot")
      CATCH
         oWord.Documents.Add()
      ENDTRY
      oWord.Selection.InsertAfter(lcText1)
      oWord.Selection.InsertAfter(lcText2)
   CATCH
      lReturnValue = .F.
   ENDTRY
   RETURN lReturnValue
ENDFUNC

In this example, the inner Try/Catch block traps only errors that may occur while a new document is created based on the specified template. Presumably, if that template doesn’t exist, an error will be raised and caught by the Catch-block, which will create a blank document. The code then proceeds as planned.

Note that the Catch-block may raise another error that will then be handled by the «outer» Catch-block (which simply sets the return value and gives up).

There is one potential problem here. We are assuming that the error has been caused by the fact that the template doesn’t exist. But of course, there could be a number of other scenarios causing other problems. For instance, the problem could be caused by the user closing Word right after it became visible (yes, they’d have to be very quick, but hey, this is only an example!). In our little example, this wouldn’t be a problem. Worst case, the Catch-block fails again and defaults to the outer handler, which will handle the situation appropriately. However, in many complex scenarios, we would have to look at additional error information and handle the situation appropriately.

Conditional Error Handling

Visual FoxPro has a number of functions to retrieve error information, such as Message(). However, those functions are not really adequate to make this bullet-proof, since nested errors make things a bit complicated. For this reason, Microsoft introduced an Exception object. The exception object can be invoked simply by using it on the CATCH statement:

CATCH TO oException

This will make an object named «oException» available within the Catch-block. This object has a number of properties, such as ErrorNo, Message, LineNo, Details, LineContents, and more. Using this construct, we can use the following syntax to check for errors caused by the template only:

FUNCTION Export(lcText1,lcText2)
   LOCAL lReturnValue
   lReturnValue = .T.
   TRY
      * We run the regular code
      LOCAL oWord as Word.Application
      oWord = CREATEOBJECT("Word.Application")
      oWord.Application.Visible = .T.
      TRY
         oWord.Documents.Add("MyTemplate.dot")
      CATCH TO oException
         IF oException.ErrorNo = 1429
            oWord.Documents.Add()
         ELSE
            * We have a different problem
            THROW oException
         ENDIF
      ENDTRY
      oWord.Selection.InsertAfter(lcText1)
      oWord.Selection.InsertAfter(lcText2)
   CATCH
      lReturnValue = .F.
   ENDTRY
   RETURN lReturnValue
ENDFUNC

In this example, we handle only error 1429, which is the one that is raised if the template wasn’t there. The question is: What do we do with all other errors? Well, basically, we want it to be handled the same way all other errors are handled within the outer Try-block. Therefore, we need to elevate the error to that level. We can do so using the THROW statement. This will «re-throw» the error, causing it to be handled by the outer Catch-block. (Exceptions elevated using a THROW statement will end up as user exceptions in the outer error handler. See below for more information.)

The WHEN clause of the CATCH statement can utilize any valid Visual FoxPro expression.

This is a pretty simple example. All we really check for is the error number. But, imagine we check for other conditions. For instance, we could try to find another template, or download it from somewhere, and so forth. If all of those attempts fail, we would re-throw the error. If all we wanted to check was the error number, though, we could do something even simpler:

TRY
   oWord.Documents.Add("MyTemplate.dot")
CATCH TO oEx WHEN oEx.ErrorNo = 1429
   oWord.Documents.Add()
ENDTRY

This will catch only error 1429. All other errors will be automatically elevated to the outer error handler, if there is one. Otherwise, the default VFP error dialog would be shown. Therefore, this is a shortcut that is functionally identical to the version shown in the previous example (except that the exception elevated to the outer handler will not be a user error).

What makes this feature very powerful is that there can be a number of different catch-blocks:

TRY
   oWord.Documents.Add("MyTemplate.dot")
CATCH TO oEx WHEN oEx.ErrorNo = 1429
   oWord.Documents.Add("MyOtherTemplate.doc")
CATCH TO oEx WHEN oEx.ErrorNo = 1943
   MessageBox("Stop closing Word!!!")
CATCH
   MessageBox("Something else happened!")
ENDTRY

Note that catch blocks are evaluated from top to bottom, and only one of them will run. Therefore, the chosen sequence is important. If we change this example to the following, we would see unexpected (or «expected» after you read this article) results:

TRY
   oWord.Documents.Add("MyTemplate.dot")
CATCH
   MessageBox("Something else happened!")
CATCH TO oEx WHEN oEx.ErrorNo = 1429
   oWord.Documents.Add("MyOtherTemplate.doc")
CATCH TO oEx WHEN oEx.ErrorNo = 1943
   MessageBox("Stop closing Word!!!")
ENDTRY

In this scenario, only the first catch-block will ever be executed, because it is so generic, it will catch all the errors and the subsequent catch statements will never be evaluated.

The WHEN clause of the CATCH statement can utilize any valid Visual FoxPro expression. Note, however, that to avoid having an erroneous catch statement you shouldn’t make these statements too complex.

Throwing Custom Errors

As we have seen in previous examples, the new THROW command can be used to elevate errors the error handler chooses not to handle, so an outer error handler (perhaps another Catch-block, or some other type of error handler) can attempt to handle the error. What’s not as obvious is that THROW can be used to raise custom errors, allowing us to architect our applications in an entirely different fashion.

Listing 1 shows an example for this technique. In this example, we have a class called CreditCard that simulates a credit card charging object. This object is rather simple. All it has is one method called ChargeCard(), and all that method does is check if the passed credit card number is «12345678». If so, the card is considered valid. This is a simplistic example, but all we are really interested in is the error handling. So let’s see what happens when the card number is invalid.

First of all, the ChargeCard() method instantiates a class called CreditCardException and passes some detailed error information to its constructor. This class is defined a little further down and is a simple subclass of the new Visual FoxPro Exception base class. It has a few overridden properties, and one additional one that gets set based on the value passed to the constructor. Once that object is instantiated, the CreditCard class raises an error (exception) using the THROW command and the new exception object as the expression. This will immediately halt the execution of the ChargeCard() method, and invoke whatever error handler is currently in use.

What’s not as obvious is that THROW can be used to raise custom errors, allowing us to architect our applications in an entirely different fashion.

So now let’s work our way back up towards the beginning of this listing to see how this code is invoked. The listing starts out with the instantiation of the credit card object and a call to the ChargeCard() method. The parameter passed to this method represents an invalid credit card (error handling is easier to demonstrate if things fail). All of this is wrapped into a Try/Catch block.

Note that the Catch-block traps for error 2071. All user-thrown exceptions end up as error 2071. In this particular example, those are all the errors we are really interested in. Of course, there could be other errors occurring, and those are caught by the second Catch-block. In a larger example, there could also be an outer error handler so we wouldn’t have to worry about that possibility. The second Catch-block is not required and I just included it because I’d consider it «good form.»

So what exactly happens when a user-thrown error occurs and our Catch-block kicks in? Well, first of all, there could be a number of different user-thrown errors, and we are not interested in any of them other than our custom exception. The user defined information is stored in a property called UserValue, which is a variant and could be anything. In our case, it is another exception object, since that’s what we threw, but it could be a string or any other value if the exception was thrown in the following manner:

THROW "Something is wrong!"

Since we threw an object, we can now check for detailed information on that object, such as the error number or perhaps even the class. If we discover error number 10001 (which is our custom error number), we can handle it. Otherwise, it is a different user-thrown error, and we really do not know what to do at all, so we simply elevate the error to the next level by re-throwing it.

Note that this example is not bullet-proof. The following line of code may, in fact, cause other errors:

IF oEx.UserValue.ErrorNo = 10001

If UserValue is not an object, or if it is an object but doesn’t have a property called ErrorNo, this would result in yet another exception, which would be thrown to an outer exception handler. Note that the outer exception handler would receive a FoxPro error, and not the user thrown error, which would not be a good thing at all.

At this point, you may wonder how UserValue could be an object but not have that property. The reason is simple: Just like one can throw a string or a number as the user value, one could throw any type of object as the user value. The thrown object doesn’t have to be subclassed from Exception.

One of the «gotchas» with this type of architecture is that youi should really use Try/Catch blocks to catch these user thrown errors. Technically, you can use ON ERROR to catch our CreditCardException, but it is a bit trickier to do so since no error object is available.

One last word of caution: The use of a THROW statement will always end up as a user thrown error. This means that if you intend to elevate an error from within a catch block to an outer error handler, you may be re-throwing a system error, but it will end up as a user error in the next-level error handler. The original (system) exception object will end up as the UserValue. Of course, to handle these situations correctly, the outer exception handler needs to be aware of this.

Mixing Error Handling Methodologies

Structured error handling is great and will replace traditional error handling in most scenarios. In fact, some modern languages like C# have only structured error handling. However, there are some downsides to structured error handling, such as no intrinsic retry capability. Also, in many scenarios, pre-existing, non-structured error handling may be in place.

So let’s look at a few examples of mixed error handling and the effects it may have on your code. Let’s start out with a simple one:

TRY
   ON ERROR MESSAGEBOX(MESSAGE())
   xxxxx
   ON ERROR
   xxxxx
CATCH
   MESSAGEBOX("Exception!")
ENDTRY

In this example, we define an ON ERROR statement within a Try/Catch block («xxxxx» always represents some kind of error in these examples). What would we expect to happen here?

Most people I present this to would expect the ON ERROR to handle the first problem, and the Catch-block to handle the second error. This is not the case! The Catch-block takes precedence over the ON ERROR and handles both exceptions.

At this point, you may wonder why one would ever define an ON ERROR inside a Try/Catch. In real-world environments, this is a rather common scenario. Consider this example:

TRY
   ErrTest()
CATCH
   MESSAGEBOX("Exception!")
ENDTRY

FUNCTION ErrTest
   ON ERROR MESSAGEBOX(MESSAGE())
   xxxxx
ENDFUNC

The Try/Catch wraps a simple call to another function (or method). That function apparently has its own error handling using the old ON ERROR methodology. However, the local error handling mechanism used by that function is now taken hostage by our Catch-block. As you can imagine, this may result in some surprising behavior.

We can produce a similar example using the Error() method:

TRY
   oTest = CREATEOBJECT("TestClass")
   oTest.Execute()
CATCH
   MESSAGEBOX("Exception!")
ENDTRY

DEFINE CLASS TestClass AS Custom
   FUNCTION Execute
      xxxxxx
   ENDFUNC
   
   FUNCTION Error(nError, cMethod, nLine)
      MESSAGEBOX(MESSAGE())
   ENDFUNC
ENDDEFINE

In this example, we are also calling another method that has a local error handler. However, this time the result is opposite from the previous example. The Error() event takes precedence over the Try/Catch and handles the error inside the called object.

So what would happen if we added some structured error handling to the TestClass object?

DEFINE CLASS TestClass AS Custom
   FUNCTION Execute
      TRY
         xxxxxx
      CATCH
         MESSAGEBOX("Exception 2!")
      ENDTRY
   ENDFUNC
   
   FUNCTION Error(nError, cMethod, nLine)
      MESSAGEBOX(MESSAGE())
   ENDFUNC
ENDDEFINE

In this example, the new Try/Catch will handle the error since it has been defined at a higher level of granularity.

An interesting question here is, «What happens if that Catch-block re-throws the error?»

DEFINE CLASS TestClass AS Custom
   FUNCTION Execute
      TRY
         xxxxxx
      CATCH TO oEx
         MESSAGEBOX("Error!")
         THROW oEx
      ENDTRY
   ENDFUNC
   
   FUNCTION Error(nError, cMethod, nLine)
      MESSAGEBOX(MESSAGE())
   ENDFUNC
ENDDEFINE

In this example, the Error() method will get a chance to handle the re-thrown error. The outer error handler will not have the opportunity to handle the exception, because it is not possible to elevate the error from within the Error() method because the exception object is not available there. The only option would be to throw a custom error.

Finally

I still owe you an explanation of the FINALLY statement. In many scenarios, it may seem as if FINALLY may not really be required, since the flow of the program is likely to continue after the Try/Catch section. «Likely» is the key term here. If a potential error is not handled in a Catch-block (either because there isn’t a matching Catch-block or because another exception is THROWn), code after the Try/Catch statements may not be executed at all. Consider this example:

DEFINE CLASS TestClass AS Custom
FUNCTION Execute
TRY
xxxxxx
CATCH TO oEx
MESSAGEBOX("Error!")
THROW oEx
FINALLY
MESSAGEBOX("Cleanup Code")
ENDTRY
MESSAGEBOX("More Code")
ENDFUNC
ENDDEFINE

In this example, the syntax error in the Try-block is caught by the Catch-block, just to be re-thrown again. This means that the very last MessageBox() will never be executed. However, the MessageBox() in the Finally-block will be executed in every case, even if no exception occurred.

Conclusion

Structured Error Handling is one of the most important language enhancements Visual FoxPro has seen in a while. It is very powerful and helps you tremendously in your attempts to produce bullet-proof code.

If you have any questions about this technology, feel free to email me.

  • Remove From My Forums
  • Question

  • Hello, I have a simple example of an error being thrown even though it should be handled within a TRY/CATCH.

    Does anyone know why this is happening?

    See the combo1.Requery() below:

      loFrm = CREATEOBJECT("form1")
     
      loFrm.Show
    
    
    **************************************************
    *-- Form:         form1 (c:\azb-tfs-vm\test\trycatchtest.scx)
    *-- ParentClass:  form
    *-- BaseClass:    form
    *-- Time Stamp:   12/29/11 10:25:05 AM
    *
    DEFINE CLASS form1 AS form
    
    
    	Top = 0
    	Left = 0
    	Height = 129
    	Width = 291
    	DoCreate = .T.
    	Caption = "Form1"
    	Name = "Form1"
    	WindowType = 1
    
    	ADD OBJECT command1 AS commandbutton WITH ;
    		Top = 66, ;
    		Left = 96, ;
    		Height = 27, ;
    		Width = 96, ;
    		Caption = "Command1", ;
    		Name = "Command1"
    
    
    	ADD OBJECT combo1 AS combobox WITH ;
    		Height = 24, ;
    		Left = 96, ;
    		Top = 24, ;
    		Width = 100, ;
    		Name = "Combo1"
    
    
    	PROCEDURE Error
    		LPARAMETERS nError, cMethod, nLine
    		MESSAGEBOX("Form Error Event")
    	ENDPROC
    
    
    	PROCEDURE Init
    		ON ERROR MESSAGEBOX("On Error Message")
    	ENDPROC
    
    
    	PROCEDURE Unload
    		ON ERROR 
    	ENDPROC
    
    
    	PROCEDURE command1.Error
    		LPARAMETERS nError, cMethod, nLine
    		MESSAGEBOX("Command1 Error Event")
    		RETURN
    	ENDPROC
    
    
    	PROCEDURE command1.Click
    		thisform.combo1.Requery()
    	ENDPROC
    
    
    	PROCEDURE combo1.Requery
    
    		*!* this will throw an error and pass it on to
    		*!* the combo1 Error event.
    		TRY
    			this.AddListItem(.F.,1,1)
    		CATCH
    		ENDTRY
    		
    		*!* this will be handled by the TRY/CATCH block
    		TRY
    			ERROR 1
    		CATCH
    		ENDTRY	
    
    	ENDPROC
    
    	PROCEDURE combo1.Error
    		LPARAMETERS nError, cMethod, nLine
    		MESSAGEBOX("Combo1 Error Event")
    	ENDPROC
    
    ENDDEFINE
    *
    *-- EndDefine: form1
    **************************************************
    

    • Edited by

      Thursday, December 29, 2011 3:45 PM

Answers

  • Look at the «Error Handler Priority» help topic. It says:

    Error takes precedence when:

    • An error occurs inside method code but outside a TRY block.

    • An error occurs in an object’s method code, and the method is called directly or is called from another method, regardless of whether the method call appears inside or outside a
      TRY block.

    So, the behaviour described does fully follow the documentation of the product.

    The latest help is available at VFPX web:
    http://vfpx.codeplex.com/

    Update: VFP does not check method parameter data type in calling method, so the error really occurs in the called method. If you would call some system function with an invalid number of parameters then the error should be reported in the calling method (and
    also during code compilation). Parameter data types are always checked at run-time.

    • Edited by
      Pavel Celba
      Thursday, December 29, 2011 4:29 PM
      Update
    • Marked as answer by
      ComputerFieldsInc_MSDN
      Thursday, December 29, 2011 4:31 PM

  • The TRY — CATCH error trapping on the «thisform.r_oXMLAdapter.LoadXML(.F.)» command is correct.

    XMLAdapter class does not have any Error event, so the nearest possibility is the TRY — CATCH block around the call.

    VFP help also mentions this possibility:

    If the method call is in a TRY block, and no immediate
    Error
    event or method code exists for the object, Visual FoxPro searches for
    Error code inherited from the parent class or another class up the class hierarchy. If no
    Error code exists in the class hierarchy, Visual FoxPro looks for a
    CATCH block corresponding to the TRY block from which the method was called.

    • Marked as answer by
      ComputerFieldsInc_MSDN
      Thursday, December 29, 2011 6:01 PM

INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Contact US

Thanks. We have received your request and will respond promptly.

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!

  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It’s Free!

*Tek-Tips’s functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Students Click Here

Try…Catch…Finally — how to use?

Try…Catch…Finally — how to use?

(OP)


This follows on from Thread1251-924215

I am using VFP8 but haven’t really got to grips with TRY…CATCH…FINALLY (TCF).

Here is a snippet of code (there is more code before and after this bit):

CODE

IF NOT TextCopied
   oWord = CREATEOBJECT(«Word.Application»)
   WAIT WINDOW NOWAIT «Mailmerge — copying text»
   oDoc = oWord.Documents.Open(THIS.txtLetter.Value)
   oRange = oDoc.Range()
   oRange.Copy()
   CopiedText = _CLIPTEXT
   && ….assign the clipboard contents to a variable
   &&     to prevent problems should the user copy
   &&     some text to the clipboard in another application
   TextCopied = .T.
ENDIF

The line oDoc = oWord.Documents.Open(THIS.txtLetter.Value) sometimes causes an error.

To implement TCF, should I:

  1. put the whole IF…ENDIF inside the TRY and, if it caused an error, set a variable to FALSE and test that variable in the line beyond the TRY…ENDTRY to see if the rest of the code should execute
  2. put just the offending line inside the TCF structure
  3. put the preceding lines and all the lines that follow inside the TCF structure?

Thanks,

Stewart

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Join Tek-Tips® Today!

Join your peers on the Internet’s largest technical computer professional community.
It’s easy to join and it’s free.

Here’s Why Members Love Tek-Tips Forums:

  • Tek-Tips ForumsTalk To Other Members
  • Notification Of Responses To Questions
  • Favorite Forums One Click Access
  • Keyword Search Of All Posts, And More…

Register now while it’s still free!

Already a member? Close this window and log in.

Join Us             Close

After years of primitive error handling, both Visual Basic and Visual FoxPro have finally acquired real error-handling capabilities with the TRY…CATCH block. And Visual Basic .NET actually has more debugging features than does FoxPro.

TRY…CATCH

The syntax for TRY…CATCH is slightly different for Visual Basic .NET than for Visual FoxPro.

Here’s the syntax in Visual FoxPro:

 

 TRY       Code here Catch To oEx WHEN ERROR() = 1234       What to do for this error Catch To oEx WHEN ERROR() = 4321       What to do for this error Catch To oEx       What to do for all other errors Finally       Do this whether there was an error or not ENDTRY 

At a minimum, you can print out the error details:

 

 Msg = oEx.Message + CHR(13)+oEx.Details + CHR(13)+oEx.LineContents MessageBox ( Msg, 32, "Error" ) 

And this is the syntax in Visual Basic .NET:

 

 Try       Code here Catch oEx as ExceptionType1  (of about 60 exception types)       Code to handle ExceptionType1 Catch oEx as ExceptionType2  (of about 60 exception types)       Code to handle ExceptionType2 Catch oEx as Exception       Code to handle all other Exceptions Finally       Do this under all circumstances END Try 

Similarly, you can print out oEx.Message + CHR(13)+oEx.Source to see the error message and offending code.

Visual Basic does have additional language elements ( Error , Raise , GetException , Err , On Error , and Resume ), but Try…Catch is a lot better.

Error trapping is meant to allow graceful handling of unavoidable errors in the program, due to device unavailability (CD-ROM, printers, floppy drives ), incorrect input that can’t be prevented, and other errors that are the result of normal program operation. Error trapping is not meant to be used to catch errors in program logic. Still, it’s better than letting users see messages from the FoxPro or the .NET IDE.

In the code that appears in this book, we have left out error trapping in most code samples. That doesn’t mean that we recommend leaving it out of your code. In fact, Try…Catch blocks are an excellent idea, and should be used in every case where a user or system error is possible. (It can even be helpful in finding your own coding errors during debugging, although that’s not the purpose of Try…Catch.) But we’ve decided to focus on the main purpose of each code fragment, and adding error trapping to a single line of code tends to make the code harder to read. So with regard to error trapping, do as I say, not as I do.

Debugging

Debugging support is awesome in both languages. In FoxPro, you can either toggle a breakpoint by clicking in the selection margin of the code at the point where you want the code to break, or you can insert a SET STEP ON command. In Visual Basic .NET, you can either toggle a breakpoint by clicking in the selection margin of the code at the point where you want the code to break, or you can insert a Debugger.Break command.

FoxPro Debugging Aids

At this point FoxPro provides you with five windows :

  • Trace (to step through the code)

  • Locals (all variables and objects)

  • Watch (type in the names of the variables to inspect)

  • Call Stack ( A called B called C , and so on)

  • Debug Output ( Debug.print output goes here)

In addition, you can add assertions to your code. Assertions are statements that only execute if they evaluate to False , and only if you SET ASSERTS ON . You’ll get a little dialog window that asks if you want to debug. This permits bracketing of debugging code in a way that it can be turned on and off with a single command in the command window.

Visual Basic .NET Debugging Aids

In Visual Basic .NET, Debugger.Break (or a breakpoint entered in the Selection Margin of the code window) gives you a vast array of debugging windows:

  • Modules

  • Threads

  • Call Stack

  • Me

  • Command window

  • Locals

  • Autos

  • Watch (four of them!)

  • Running Documents

  • Breakpoints

Each of these windows gives you some of the information about your program’s state at the breakpoint. You can either go to the command window in Immediate mode and print the contents of individual Variables and object properties, type the name of the object in a watch window, or look in the Locals window to see if it’s there already.

It’s clear that both languages offer excellent error trapping and debugging capabilities.

Понравилась статья? Поделить с друзьями:
  • From docx import document ошибка
  • Four c service requ 135 ошибка
  • From django db import models ошибка
  • Forza horizon 5 ошибка загрузки
  • Fose loader fallout 3 ошибка