MS Access - How to prevent crashes in VBA? - vba

I'm new to VBA in Access. I have set up a query that uses some VBA to requery on a combo box selection. This works great however every so often Access will randomly crash.
Is there any "if error" statement I can put in my VBA code that will pop up a message rather than completely crashing?

Error handling in your application is a separate issue from Access crashing. Even if you reach the low limits of Access (https://support.office.com/en-us/article/Access-2010-specifications-1E521481-7F9A-46F7-8ED9-EA9DFF1FA854), it shouldn't be crashing, but giving you an error message. That's a bug not of your doing. I've used Access for a while, and sometimes you have to work around the bugs, as well as the limits.
Sometimes, the VBA compiler fails to get things right after you make a change. If you Cut and Paste the text of an entire Module, it seems to force the compiler to recompile that whole Module. Always use the Debug > Compile menu item to compile and check your code. Then save.
Sometimes, doing the Compact and Repair will resolve problems. In fact, you need to Compact and Repair, because it seems that the file will bloat non-stop otherwise, and it has a 2GB limit. Do a backup first, because the Compact and Repair sometimes fails, leaving you with garbage.
Magisch gave an example of error handling in VB, which is something you should do to write a robust application (see http://www.vb6.us/tutorials/error-handling).

First of all, your an ordinary error shouldn't cause access to crash. If it does, you probably need to clean up your file some, for instance by repairing or de/re compiling it.
Secondly, if you want to handle errors in a simple way you can do it roughly like this:
Public Function doStuff (myInput as Integer) as Integer
On Error GoTo Error_Handling
'something that may cause an error to occur
Exit Function 'Important so your error handling doesn't get executed every time the function runs regardless
Error_Handling:
'something that you want to happen when the error occurs
End Function

Related

Word VBA Document.ClosePrintPreview intermittently crashes with runtime error 4605

I am experiencing intermittent runtime error when doing the following in Word 2007 VBA:
Doc.PrintPreview
Doc.ClosePrintPreview
Here Doc is a valid document that was successfully opened by the same VBA script (not by the user).
Basically, I need print preview to open (for the purposes of forcing the update of all the fields) and then close it right away. The problem is that in about 20% of cases ClosePrintPreview crashes with runtime error 4605, complaining that the document is not in print preview mode, as if previous PrintPreview method call never did its job.
I can't visually confirm whether the PrintPreview actually did its job because the whole thing happens in pretty busy and tight loop, so Word does not update anything on the screen (even though I never turned off Application.ScreenUpdating).
It happens when the script is processing large documents that have several thousands of fields (print preview takes time to be generated when called interactively). It does not happen on simple, small-size documents.
And the whole thing happens intermittently, so I really can't figure out what triggers it. In about 80% of cases the script runs just fine.
So, the question is whether it is PrintPreview that really fails (which is not good and would need to be addressed, because I call it for a reason) or is it just ClosePrintPreview that gets confused and I could safeguard it with something like
If Application.PrintPreview = True Then Doc.ClosePrintPreview
Has anyone seen anything similar?

VBA code example on Microsoft's website destroyed the clipboard for the rest of the computer. Why did this happen and how can I prevent it?

This is something I didn't think was even possible, but here goes. I was trying to learn how to use the Windows API in Visual Basic to use system calls, and this tutorial (yes, I had to type out the link manually to ask this question, more on that later) showed me how to use the clipboard to retrieve text that the user copied with Ctrl+C. Out of curiosity, and under the assumption that all user input is bad input, I tried pressing Print Screen and then running the code just to see what would happen. I got some error message (can't remember what) but what's very strange is, now the clipboard no longer works! Any attempt I make to paste after a cut or copy, no matter what program I'm using, either does nothing or returns an error message in the program I'm using it in. Yes, it's my fault for intentionally trying to break the code example, but let's be honest - there's no excuse for the OS to fall apart so easily. If it matters, I'm using a PC running Windows 10.
EDIT: Settings won't let me clear the clipboard, and when I try to view the clipboard history, it shows nothing is there. Unfortunately I wasn't able to screenshot the clipboard history because it closes by itself when I try to open Snipping Tool.
Sounds like you've missed a CloseClipboard(), keeping the clipboard locked since Windows thinks a program is reading to it or writing from it. This will prevent other programs from working with the clipboard, since only one program can access it at a time. If Access is still open, you can try running CloseClipboard in the immediate window, else, I recommend a reboot.
On code like this, always add an error handler that calls CloseClipboard() to prevent leaving the clipboard open if something unexpected happens. Note that when working with WinAPI, you might encounter hard crashes that may not call the error handler, so always triple-check your pointers and expect crashes and reboots.
The code you've found is also not adjusted for 64-bit use, so beware. If you've got it to work by just slapping PtrSafe on the functions, you may end up with invalid pointers which can crash Access, leaving the clipboard open and unusable.
The code you've found, while written by Microsoft, is not of particularly good quality. I recommend first checking if there's text on the clipboard using EnumClipboardFormats, then only requesting text if there actually is text on the clipboard.
Beware that using WinAPI through VBA is tough, it's not beginner stuff, especially regarding the clipboard.
Note that there's no excuse for the OS to fall apart so easily is not the attitude to have when working with WinAPI. You're directly interfacing with the OS without any of the securities that managed languages offer, and manually working with pointers. It can and will break if you do something invalid. There's a reason most people use libraries that abstract the dangerous stuff away, if you don't, all bets are off.
similar problem for me. I did not have a CloseClipboard() but when I looked in another module there was already a EmptyClipboard() so used that before the DupRec() call at each instance and no more problems with clipboard. just FYI

Testing for DLL references in VBA

First, apologies for the generic title - if anyone can suggest a better one, I'd be happy to change it, but right now, I have no clue where to start.
I have a workbook utilizing a DLL to access a data provider (Bloomberg), and the requirements to get it to work correctly are quite tricky. Furthermore, deployment is a nightmare, since users might need to reference the DLL themselves.
Naturally, I first check wether the library is referenced, before testing the library itself.
Here's my code (which is working as intended) :
Public Sub TestBloomberg()
Dim ref As Object
Dim fRef As Boolean
fRef = False
For Each ref In ThisWorkbook.VBProject.References
If ref.GUID = "{4AC751C2-BB10-4702-BB05-791D93BB461C}" Then
If Not ref.IsBroken Then
fRef = True
End If
End If
Next
If fRef Then
' In separate Sub to get around User-defined type error
Call TestBloombergConnection
ElseIf Not fRef Then
' warn user about missing reference
End If
End Sub
As you can see, if the reference to the DLL is set, I proceed checking if the library works as intended (this has a lot of external factors at play, such as wether the server-application is running, the user is logged in, etc.) You can think of this as a simple ON-ERROR-GOTO-wrapped call to the dll.
I am forced to move the actual test of the functionality to another sub, as called from the second if-block. If I have no (or a broken) reference to the dll, even though the library will not be called itself, I will get a User-defined Error. If I move the exact same code to another sub, it will work perfectly.
Finally, my question:
What happens when I run my VBA code, why do I get a (i think) runtime error during compile time? How can my code be so dependend on external factors, that it can't even get to the point of failing?
What this behavior demonstrates is that VBA compiles separate subroutines separately and at different times. I had a similar situation when I was trying to resolve references on behalf of the users (solving a versioning problem, which I got to work, but then abandoned as not worth the trouble).
When you are ready to enter a subroutine, it interprets only as much as it needs to, and you could get a compile time error then, even though to you it seems like you are at run time.
The error you are actually getting is probably a 429 Automation error or something similar. You would get that if you have a broken link (dll moved, deleted, or not registered). What I remember from my project, is that I could reliably handle it if a good reference was saved in the file, or no reference was saved, but if a bad reference was saved, the code had to be isolated similar to what you found. I think I had mine in a separate class, but the principle is the same.
I think this "interpret only as much as necessary" is considered a feature of VBA. You can run a certain section of code, even if you have compile errors elsewhere. This can be useful when you only have a partially written functions or other half-finished stuff, or if you open the file on a computer without some referenced software installed. It lets at least some of the functionality still work.

Can't eliminate Access corruption

My firm's Access database has been having some serious problems recently. The errors we're getting seem like they indicate corruption -- here are the most common:
Error accessing file. Network connection may have been lost.
There was an error compiling this function.
No error, Access just crashes completely.
I've noticed that these errors only happen with a compiled database. If I decompile it, it works fine. If I take an uncompiled database and compile it, it works fine -- until the next time I try to open it. It appears that compiling the database into a .ACCDE file solves the problem, which is what I've been doing, but one person has reported that the issue returned for her, which has me very nervous.
I've tried exporting all of the objects in the database to text, starting with a brand new database, and importing them all again, but that doesn't solve the problem. Once I import all of the objects into the clean database, the problem comes back.
One last point that seems be related, but I don't understand how. The problem started right around the time that I added some class modules to the database. These class modules use the VBA Implements keyword, in an effort to clean up my code by introducing some polymorphism. I don't know why this would cause the problem, but the timing seems to indicate a relationship.
I've been searching for an explanation, but haven't found one yet. Does anyone have any suggestions?
EDIT: The database includes a few references in addition to the standard ones:
Microsoft ActiveX Data Objects 2.8
Microsoft Office 12.0
Microsoft Scripting Runtime
Microsoft VBScript Regular Expressions 5.5
Some of the things I do and use when debugging Access:
Test my app in a number of VM. You can use HyperV on Win8, VMWare or VirtualBox to set up various controlled test environments, like testing on WinXP, Win7, Win8, 32bit or 64 bits, just anything that matches the range of OS and bitness of your users.
I use vbWatchDog, a clever utility that only adds a few classes to your application (no external dependency) and allows you to trap errors at high level, and show you exactly where they happen. This is invaluable to catch and record strange errors, especially in the field.
If the issue appears isolated to one or a few users only, I would try to find out what is special about their config. If nothing seems out of place, I would completely unsintall all Office component and re-install it after scrubbing the registry for dangling keys and removing all traces of folders from the old install.
If your users do not need a complete version of Access, just use the free Access Runtime on their machine.
Make sure that you are using consistent versions of Access throughout: if you are using Access 2007, make sure your dev machine is also using that version and that all other users are also only using that version and that no components from Access 2010/2013 are present.
Try to ascertain if the crash is always happening around the same user-actions. I use a simple log file that I write to when a debugging flag is set. The log file is a simple text file that I open/write to/close everytime I log something (I don't keep it open to make sure the data is flushed to the file, otherwise when Access crashes, you may only have old data in the log file as the new one may still be in the buffer). Things I log are, for instance, sensitive function entry/exit, SQL queries that I execute from code, form open/close, etc.
As a generality, make sure your app compiles without issue (I mean when doing Debug > Compile from the IDE). Any issue at this stage must be solved.
Make absolutely sure you close all open recordsets, preferrably followed by setting their variables to Nothing. VBA is not as sensitive as it used to be about dangling references, but I found it good practice, especially when you cannot be absolutely sure that these references will be freed (especially when doing stuff at Module-level or Class-level for instance, where the scope may be longer-lived than expected).
Similarly, make sure you properly destroy any COM object you create in your classes (and subs/functions. The Class_Terminate destructor must explicitly clean up all. This is also valid when closing forms if you created COM objects (you mentioned using ADOX, scripting objects and regex). In general keeping track of created objects is paramount: make sure you explicitly free all your objects by resetting them (for instance using RemoveAll on a dictionary, then assigning their reference to Nothing.
Do not over-use On Error Resume or On Error Goto. I almost never use these except when absolutely necessary to recover from otherwise undetectable errors. Using these error trapping constructs can hide a lot of errors that would otherwise show you that something is wrong with your code. I prefer to program defensively than having to handle exceptions.
For testing, disable your error trapping to see if it isn't hiding the cause of your crashes.
Make sure that the front-end is local to the user machine, You mention they get their individual front-end from the network but I'm not sure if they run it from there or if it it copied on their local machine. At any rate, it should be local not on a remote folder.
You mention using SQL Server as a backend. Try to trace all the queries being executed. It's possible that the issue comes from communication with SQL Server, a corrupt driver, a security issue that prevents some queries from being run, a query returning unexpected data, etc. Watch the log files and event log on the server closely for strange errors, especially if they involve security.
Speaking of event log, look for the trace of the crash in the event log of your users. There may be information there, however cryptic.
If you use custom ribbon actions, make sure thy are not causing issues. I had strange problems over time with the ribbon. Log all all function calls made by the ribbon.

How to refactor VB6 code to prevent run-time errors

A VB6 app is experiencing a run-time errors at a variety of places.
I know this is the result of poor error handling, but is it possible to analyse the code to see where it is susceptible to run-time errors?
Any application is susceptible to run-time errors where there is no error handling around calls to external resources, so you could identify those points as a start.
I've used a free-tool (many years ago) that could retro-fit error handling to VB6 code, which would at least log errors and the point that they occurred.
Here it is: The HuntErr Addin for easy error handling in VB6
You need to make sure that every one of the methods (functions, subs, properties...) in your code base has an error handling statement. It's probably true that not every single one can generate a run time error, but that will protect the application from crashing without a lot of upfront analysis.
Make sure there's a statement before any executable line of code that says "On Error GoTo..." with a label, and then make sure to put that label with some error handling code at the bottom of the method. I've used a free tool called MZ-Tools 3.0 that allows you to automate the inclusion of this text. There is an Error Handler tab in the options that lets use specify what text you want to put in and where. This is what mine looks like:
On Error GoTo l{PROCEDURE_NAME}_Error
{PROCEDURE_BODY}
Exit {PROCEDURE_TYPE}
l{PROCEDURE_NAME}_Error:
LogError "{MODULE_NAME}", "{PROCEDURE_NAME}", Err, Err.Description
Then I just make sure that the LogError function exists and writes the error out to a log file that I can review.
Common sources of run-time errors in VB6 apps include
Accessing a key in a collection that doesn't exist
Calling a method or property on an object that is nothing
Using CLng to convert a string to a number when the string is null
Accessing an array beyond its length (like after calling Split and then assuming that the string has the number of pieces you expected)
So besides doing what others have suggested and analyzing where the actual errors are coming from, you could start by looking for areas such as these in your code and putting appropriate error handling around them. Keep in mind that often the best "error handling" doesn't involve using On Error at all, but preventing the error ahead of time by checking for these boundary case, like
If Not Object Is Nothing
If Len(string) > 0
If UBound(array) > x
etc...
There are some good answers here with both the On Error GoTo recommendations and the common errors that fall through the cracks that bwarner mentions.
But maybe widen the scope and utilize built-in tools to analyze code like breakpoints, watch expressions, and especially good for debugging run-time errors, the locals window (often overlooked in debugging, but very powerful) and the call stack. You can get a lot of great info on that from here: Debugging Your Code and Handling Errors
Other things to think about that may be helpful:
Invest in a tool that will help you
with the analysis like CodeSMART
2009 for VB6 or VB Project
Analyzer.
Try to port the existing application
to VB.NET - not to actually port and
use, but to view the conversion log
for things that need to be fixed.
Following Ryan's answer and the comment in response, you don't have to put error handling in every routine, just every Event and Sub Main() (and API callbacks if they don't already have it).
API callbacks refer to routines called directly by the Win32API, most often passed to Declared functions using AddressOf. (I.e. search your code for AddressOf and ensure all routines mentioned as arguments have error handlers that catch errors and do not allow them to attempt to bubble up.)
And I've just noticed this doesn't really answer the original question asked (although given the comment in response to Ryan's answer it is a good first step): Once you have error handling in every Event, etc, you will catch all errors, but you won't be able to directly analyse where all errors occur. You will need to extend the error logging to at least all the routines called by the events that log errors to more accurately locate the exact source of each error.
In VB6 a Runtime error occurs exactly when an Event function is called without error handling. So at least all your event handling functions (like Form.Open()) should be surrounded by an error handler (yes, I know VB6 does not have them), which can nicely be implemented like this (We do it in all our applications like this):
Use this as the first line of EVERY event handling function (it is a large valid label which sets the On Error at the end):
¦¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯: On Error GoTo ErrorHandler:
Now use this on the end of all those functions:
¦____________________________________________________________________________________________________________________________________: Exit Sub
ErrorHandler: handleError CodeDb, "Form_frm_filter_deletecolum", "cmd_deletecolum_Click", err.Number, err.description, err.Source, err.LastDllError, err.Helpfile, err.HelpContext, Erl: err.Clear
But replace the two strings with the module name and function name. AND replace each On Error Goto 0 with On Error Goto ErrorHandler.
Now create a function handleError with the given arguments (in my app it automatically sends a bug report into our bugtracking system), and display a nice error message.
We even pushed this a bit further, by having a prebuilt process that adds similar lines to all other (means non-event functions, or functions just called by other functions), to remember the line in which the error occured and to accumulate a complete stack trace (Yeah, stacktraces in VB6!). In addition this process adds line numbers to each line so they are given in the Erl part of the error handler.
Our tool is written as some MS Access modules, so I can't simply provide it for you, but you see where you have to go for it.