Home » excel » excel – In VBA, how to call a specific method from an unkown module without Application.Run()?

excel – In VBA, how to call a specific method from an unkown module without Application.Run()?

Posted by: admin April 23, 2020 Leave a comment


I’m trying to improvise a Drupal-like hook mechanism in VBA (go ahead and criticise, I know it sounds stupid). The reason of my endeavour is that I’ve found no other way of properly dividing the work across a team, so with this mechanism I hope to bring a little of Drupal’s tried-and-true hook invocation system. I’ve done it, it works nicely, but I have a little shortcoming.

Here’s what I’ve done: a dispatcher module, which basically loops over all the modules in my project, and tests whether they start with a specific prefix (a hint that they’re hook modules), and when it finds one, it does this:

Call Application.Run(Module.Name & ".hook_" & HookName)

Not very original, I know, but if I loop over all the modules and invoke hooks for all Workbook events, it’s beginning to smell a little like AOP. This means I allow any number of modules to act upon, say, Workbook_SheetChange, without polluting the code in ThisWorkbook. Even better, different people will work on different features inside different hook modules (BIG BONUS).

As I said, this works, but I must also call Application.EnableEvents = False before these invocations, and Application.EnableEvents = True after the invocations, so I don’t end up in infinite invocation loops. This is ok too.

My problem: I’d like to make a general error handler above all hooks, so that if one hook screws something up, I could catch the error inside my top dispatcher and re-enable events. Sounds like a good idea, but because I use Application.Run(), the whole error-handling mechanism gets broken in the middle, so the dispatcher won’t receive any error that happens inside a hook that is invoked like that. This will also leave the application events set to False, which is bad (remember that I’m setting them to False just before I invoke the hook).

My question: Is there a way to invoke a specifically named function inside an unkown module without Application.Run, so my errors bubble up to the dispatcher? I tried this:

Call Module.hook_WorksheetChange()

But it didn’t compile (I wasn’t holding my breath over its success, but I hoped…). Here, Module is an Object that holds the VBComponent, and hook_WorksheetChange() is an actual Sub defined in a module.

Ideas, please? It wouldn’t be too elegant to let every hook always deal with the Application.EnableEvents = True cleanup – it should only be concerned with it’s own, feature-specific error handling.

How to&Answers:

If you’re doing your error handling in the way described in this book, then you should be fine.

Basically Bovey et al. give make every entry point routine a Sub and every non-entry point routine a Function. All functions return a boolean indicating the error status. All errors bubble up to the top. It works very well.

The only question here was whether or not Application.Run can return a value. I’ve just checked, and it can.

I strongly recommend the book, but for the sake of completeness, I’ve put the the templates they recommend below.

Hope that helps. Oh, and if you’re going to be doing complex stuff in Excel/VBA, read their book.

Entry point routines

Public Sub test()
    Const sSOURCE As String = "test"
    On Error GoTo ErrorHandler

    ' Your code goes here
    If Not Application.Run("YourModule.YourFunction") Then Err.Raise glHANDLED_ERROR
    ' all non-entry routines are called with this If ... Then structure

    Exit Sub

    If bCentralErrorHandler(m_sModule, sSOURCE, , True) Then
        Resume ErrorExit
    End If
End Sub

Non entry point routines

Private Function MyFunction(SomeParameter)

    Const sSOURCE As String = "MyFunction"
    Dim bReturn As Boolean
    bReturn = True
    On Error GoTo ErrorHandler

    ' your code goes here


    MyFunction = bReturn
    Exit Function


    bReturn = False
    If bCentralErrorHandler(m_sModule, sSOURCE) Then
        Resume ErrorExit
    End If

End Function

Central Error-Handling Routine

' Description:  This module contains the central error
'               handler and related constant declarations.
' Authors:      Stephen Bullen, www.oaltd.co.uk
'               Rob Bovey, www.appspro.com
' Chapter Change Overview
' Ch#   Comment
' --------------------------------------------------------------
' 12    Initial version
Option Explicit
Option Private Module

' **************************************************************
' Global Constant Declarations Follow
' **************************************************************
Public Const gbDEBUG_MODE As Boolean = False    ' True enables debug mode, False disables it.
Public Const glHANDLED_ERROR As Long = 9999     ' Run-time error number for our custom errors.
Public Const glUSER_CANCEL As Long = 18         ' The error number generated when the user cancels program execution.

' **************************************************************
' Module Constant Declarations Follow
' **************************************************************
Private Const msSILENT_ERROR As String = "UserCancel"   ' Used by the central error handler to bail out silently on user cancel.
Private Const msFILE_ERROR_LOG As String = "GHQ_Error.log"  ' The name of the file where error messages will be logged to.

' Comments: This is the central error handling procedure for the
'           program. It logs and displays any run-time errors
'           that occur during program execution.
' Arguments:    sModule         The module in which the error occured.
'               sProc           The procedure in which the error occured.
'               sFile           (Optional) For multiple-workbook
'                               projects this is the name of the
'                               workbook in which the error occured.
'               bEntryPoint     (Optional) True if this call is
'                               being made from an entry point
'                               procedure. If so, an error message
'                               will be displayed to the user.
' Returns:      Boolean         True if the program is in debug
'                               mode, False if it is not.
' Date          Developer       Chap    Action
' --------------------------------------------------------------
' 05/28/04      Rob Bovey       Ch12    Initial version
Public Function bCentralErrorHandler( _
            ByVal sModule As String, _
            ByVal sProc As String, _
            Optional ByVal sFile As String, _
            Optional ByVal bEntryPoint As Boolean, _
            Optional bShowDesc As Boolean) As Boolean

    Static sErrMsg As String

    Dim iFile As Integer
    Dim lErrNum As Long
    Dim sFullSource As String
    Dim sPath As String
    Dim sLogText As String

    ' Grab the error info before it's cleared by
    ' On Error Resume Next below.
    lErrNum = Err.Number
    ' If this is a user cancel, set the silent error flag
    ' message. This will cause the error to be ignored.
    If lErrNum = glUSER_CANCEL Then sErrMsg = msSILENT_ERROR
    ' If this is the originating error, the static error
    ' message variable will be empty. In that case, store
    ' the originating error message in the static variable.
    If Len(sErrMsg) = 0 Or bShowDesc Then sErrMsg = Err.description
    If Erl > 0 Then sErrMsg = sErrMsg & " at line " & Erl

    ' We cannot allow errors in the central error handler.
    On Error Resume Next

    ' Load the default filename if required.
    If Len(sFile) = 0 Then sFile = ThisWorkbook.name

    ' Get the gxlapp directory.
    sPath = ThisWorkbook.Path
    If Right$(sPath, 1) <> "\" Then sPath = sPath & "\"

    ' Construct the fully-qualified error source name.
    sFullSource = "[" & sFile & "]" & sModule & "." & sProc

    ' Create the error text to be logged.
    sLogText = "  " & sFullSource & ", Error " & _
                        CStr(lErrNum) & ": " & sErrMsg & IIf(Erl > 0, ". Line: " & Erl, "")

    ' Open the log file, write out the error information and
    ' close the log file.
    iFile = FreeFile()
    Open sPath & msFILE_ERROR_LOG For Append As #iFile
    Print #iFile, Format$(Now(), "mm/dd/yy hh:mm:ss"); sLogText
    If bEntryPoint Then Print #iFile,
    Close #iFile

    ' Do not display or debug silent errors.
    If sErrMsg <> msSILENT_ERROR Then

        ' Show the error message when we reach the entry point
        ' procedure or immediately if we are in debug mode.
        If bEntryPoint Or gbDEBUG_MODE Then
            gxlApp.ScreenUpdating = True
            MsgBox sErrMsg
            ' Clear the static error message variable once
            ' we've reached the entry point so that we're ready
            ' to handle the next error.
            sErrMsg = vbNullString
        End If

        ' The return vale is the debug mode status.
        bCentralErrorHandler = gbDEBUG_MODE

        ' If this is a silent error, clear the static error
        ' message variable when we reach the entry point.
        If bEntryPoint Then sErrMsg = vbNullString
        bCentralErrorHandler = False
    End If

End Function