Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




Tip 21: Keep error text in a resource file

software


Tip 21: Keep error text in a resource file.

Resource files (RES files) are good things in which to keep your error text and messages, and most C developers use them all the time, especially if they're shipping products internationally. That said, Visual Basic itself uses resource files-recognize some of these sample strings taken from Visual Basic's own resources?



STRINGTABLE FIXED IMPURE
BEGIN
3 "Return without GoSub"
5 "Invalid procedure call or argument"
6 "Overflow"
7 "Out of memory"
.
.
.

13029 "Sa&ve Project Group"
13030 "Sav&e Project Group As..."
13031 "Ma&ke %s..."
.
.
.
23284 "Compile Error in File '|1', Line |2 : |3"
.
.
.

END

In fact, Visual Basic 6 uses a total of 2,934 resource files.

The %s in string 13031 is used to indicate (to a standard C library function) wh 525n1322f ere a substring should be inserted-the binary name (?.EXE, ?.DLL, ?.OCX) in this case. The |1, |2, and |3 in string 23284 shows where replacement strings should be inserted, this time using a different technique. In fact, this latter technique (which you can use even on the %s strings) can be seen operating if you look at ResolveResString in the Visual Basic source code for SETUP1.VBP. It looks more or less like this (this is slightly tidied up):


' FUNCTION: ResolveResString
' Reads string resource and replaces given macros with given
' values

' Example, given a resource number of, say, 14:
"Could not read '|1' in drive |2"
' The call
ResolveResString(14, "|1", "TXTFILE.TXT", "|2", "A:")
' would return the string
"Could not read 'TXTFILE.TXT' in drive A:"

' IN: [nResID] - resource identifier
[vReplacements] - pairs of macro/replacement value


Public Function ResolveResString( _
ByVal nResID As Integer _
, ParamArray vReplacements() As Variant _
) As String

Dim nMacro As Integer
Dim sResString As String

sResString = LoadResString(nResID)

' For each macro/value pair passed in ...
For nMacro = LBound(vReplacements) To UBound(vReplacements) Step 2

Dim sMacro As String
Dim sValue As String

sMacro = CStr(vReplacements(nMacro))
sValue = vbNullString

If nMacro < UBound(vReplacements) Then
sValue = vReplacements(nMacro + 1)
End If

' Replace all occurrences of sMacro with sValue.
Dim nPos As Integer

Do
nPos = InStr(sResString, sMacro)

If 0 <> nPos Then
sResString = Left$(sResString, nPos - 1) & _
sValue & _
Mid$(sResString, nPos + Len(sMacro))
End If

Loop Until nPos = 0

Next nMacro

ResolveResString = sResString

End Function

To see all this code work, compile the strings and add them to your project. (Save the strings as an RC file, and then run the resource compiler on the RC file like so: C:\rc -r ?.rc. Add the resulting RES file to your application by selecting Add File from the Project menu.) Then add this code to Form1's Load event:

MsgBox ResolveResString( _
23284 _
, "|1" _
, "Fubar.bas" _
, "|2" _
, "42" _
, "|3" _
, ResolveResString(7) _
)

This will produce the following message box:

Keeping message text in a resource file keeps strings (which could include SQL strings) neatly together in one place, and also flags them as discardable data, stuff that Windows can throw away if it must. Don't worry about this-Windows can reload strings from your binary image if it needs them. Your code is treated in exactly the same way and you've never worried about that being discarded, have you? Keeping read-only data together like this allows Visual Basic and Windows to better optimize how they use memory. Resource files also provide something akin to reuse as they usually allow you to be "cleverer" in your building of SQL and error text-they might even provide a way for you to share data like this across several applications and components. (See the notes on using a ROOS earlier in this chapter.)

Tip 22: Always handle errors in controls and components (that you build).

Errors in controls

When a control raises an unhandled error (by the control), the error is reported and the control becomes disabled-it actually appears hatched-or the application terminates. (See Figure 1-7 and Figure 1-8.)

Figure 1-7 Containing form before the error

Figure 1-8 Containing form after the error

It's important to know that errors in a UserControl can be propagated to two different levels. If the errors are caused wholly by the control, they will be handled by the control only. If the errors are instigated via a call to an external interface on the control, from the containing application, they will be handled by the container. Another way to state this is to say that whatever is at the top of the call stack will handle unhandled errors. If you call into a control, say from a menu selection in the container, the first entry in your call stack will be the container's code. That's where the mnuWhatever_Click occurred. If the control raises an error now, the call stack is searched for a handler, all the way to the top. In this case, any unhandled control error has to be handled in the container, and if you don't handle it there, you're dead when the container stops and, ergo, so does the control. However, if the control has its own UI or maybe a button, your top-level event could be a Whatever_Click generated on the control itself. The top of your call stack is now your control code and any unhandled errors cause only the control to die. The container survives, albeit with a weird-looking control on it. (See Figure 1-8.)

This means that you must fragment your error handling across containers and controls, not an optimal option. Or you need some way of raising the error on the container even if the container's code isn't on the stack at the moment the error occurs. A sort of Container.Err.Raise thing is required.

In each of our container applications (those applications that contain UserControls), we have a class called ControlErrors (usually one instance only). This class has a bundle of miscellaneous code in it that I won't cover here, and a method that looks something like this:

Public Sub Raise(ParamArray v() As Variant)

On Error GoTo ControlErrorHandler:

' Basically turns a notification into an error -
' one easy way to populate the Err object.
Err.Raise v(0), v(1), v(2), v(3), v(4)

Exit Sub

ControlErrorHandler:

MsgBox "An error " & Err.Number & " occurred in " & Err.Source & _
" UserControl. The error is described as " & Err.Description

End Sub

In each container application we declare a new instance of ControlErrors, and for each of our UserControls we do what's shown below.

If True = UserControl1.UsesControlErrors Then

Set UserControl1.ErrObject = o

End If

UsesControlErrors returns True if the UserControl has been written to "know" about a ControlErrors object.

In each control-to complete the picture-we have something like this (UsesControlErrors is not shown):

Private ContainerControlErrors As Object

Private Sub SomeUIWidget_Click()

On Error GoTo ErrorHandler:

Err.Raise ErrorValue

Exit Sub

ErrorHandler:

' Handle top-level event error.

' Report error higher up?
If Not ContainerControlErrors Is Nothing Then

ContainerControlErrors.Raise ErrorValue

End If

End Sub

Public Property Set ErrObject(ByVal o As Object)

Set ContainerControlErrors = o

End Property

We know from this context that SomeUIWidget_Click is a top-level event handler (so we must handle errors here), and we can make a choice as to whether we handle the error locally or pass it on up the call chain. Of course, we can't issue a Resume Next from the container once we've handled the (reporting of the) error-that's normal Visual Basic. But we do at least have a mechanism whereby we can report errors to container code, perhaps signalling that we (the control) are about to perform a Resume Next or whatever.

Errors in OLE servers

Raising errors in a Visual Basic OLE Automation server is much the same as for a stand-alone application. However, some consideration must be given to the fact that your server may not be running in an environment in which errors will be visible to the user. For example, it may be running as a service on a remote machine. In these cases, consider these two points:

Don't display any error messages. If the component is running on a remote machine, or as a service with no user logged on, the user will not see the error message. This will cause the client application to lock up because the error cannot be acknowledged.

Trap every error in every procedure. If Visual Basic's default error handler were executed in a remote server, and assuming you could acknowledge the resulting message box, the result would be the death of your object. This would cause an Automation error to be generated in the client on the line where the object's method or property was invoked. Because the object has now died, you will have a reference in your client to a nonexistent object.

To handle errors in server components, first trap and log the error at the source. In each procedure, ensure that you have an Err.Raise to guarantee that the error is passed back up the call stack. When the error is raised within the top-level procedure, the error will propagate to the client. This will leave your object in a tidy state; indeed, you may continue to use the same object.

If you are raising a user-defined error within your component you should add the constant vbObjectError (&H80040000&). Using vbObjectError causes the error to be reported as an Automation error. To extract the user-defined error number, subtract vbObjectError from Err.Number. Do not use vbObjectError with Visual Basic-defined errors; otherwise, an "Invalid procedure call" error will be generated.


Document Info


Accesari: 1675
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2024 )