2010-09-25 53 views
25

Per il codice di gestione degli errori, vorrei ottenere il nome della funzione VBA corrente (o sub) in cui si è verificato l'errore. Qualcuno sa come questo potrebbe essere fatto?Ottieni il nome della funzione VBA corrente

[EDIT] Grazie a tutti, avevo sperato che esistesse un trucco non documentato per determinare autonomamente la funzione, ma ovviamente non esiste. Indovina che rimarrò con il mio codice corrente:

Option Compare Database: Option Explicit: Const cMODULE$ = "basMisc" 

Public Function gfMisc_SomeFunction$(target$) 
On Error GoTo err_handler: Const cPROC$ = "gfMisc_SomeFunction" 
    ... 
exit_handler: 
    .... 
    Exit Function 
err_handler: 
    Call gfLog_Error(cMODULE, cPROC, err, err.Description) 
    Resume exit_handler 
End Function 

risposta

3

Non utilizzare alcun modo VBA incorporato. Il meglio che riuscirai a fare è ripeterti codificando il nome del metodo come una variabile a livello di metodo costante o regolare.

Const METHOD_NAME = "GetCustomer" 

On Error Goto ErrHandler: 
' Code 

ErrHandler: 
    MsgBox "Err in " & METHOD_NAME 

Si può essere in grado di trovare qualcosa a portata di mano nella MZ Tools for VBA. È un componente aggiuntivo per sviluppatori per la famiglia di lingue VB. Scritto da un MVP.

+0

Sì, praticamente quello che ho sempre fatto, vedere il mio post modificato. Grazie. – maxhugen

3

VBA non ha alcuna traccia di stack incorporata a cui è possibile accedere in modo programmatico. Dovresti progettare il tuo stack e spingerlo/pop su quello per ottenere qualcosa di simile. Altrimenti, dovrai codificare i tuoi nomi funzione/sottotitoli nel codice.

+0

VBA ha una proprietà Application.Caller –

+0

Sì, lo fa. Ma non ha nulla a che fare con la domanda in questione. – KevenDenen

+0

No, ma ha a che fare con la tua risposta. –

14

Non c'è niente per ottenere il nome della funzione corrente, ma è possibile creare un sistema di tracciatura piuttosto leggero utilizzando il fatto che le vite degli oggetti VBA sono deterministiche. Ad esempio, è possibile avere una classe chiamata 'Tracer' con questo codice:

Private proc_ As String 

Public Sub init(proc As String) 
    proc_ = proc 
End Sub 

Private Sub Class_Terminate() 
    If Err.Number <> 0 Then 
     Debug.Print "unhandled error in " & proc_ 
    End If 
End Sub 

e quindi utilizzare tale classe nella routine come:

Public Sub sub1() 
    Dim t As Tracer: Set t = New Tracer 
    Call t.init("sub1") 

    On Error GoTo EH 

    Call sub2 

    Exit Sub 

EH: 
    Debug.Print "handled error" 
    Call Err.Clear 
End Sub 

Public Sub sub2() 
    Dim t As Tracer: Set t = New Tracer 
    Call t.init("sub2") 

    Call Err.Raise(4242) 
End Sub 

Se si esegue 'sub1', si dovrebbe ottenere questo uscita:

unhandled error in sub2 
handled error 

perché l'istanza Tracer in 'sub2' stato deterministicamente distrutta quando l'errore ha causato un'uscita dalla routine.

Questo modello generale è visto molto in C++, sotto il nome "RAII", ma funziona anche bene in VBA (oltre al fastidio generale dell'uso delle classi).

EDIT:

Per affrontare il commento di David Fenton che questa è una soluzione relativamente complicata per un semplice problema, non credo che il problema è in realtà così semplice!

Sto dando per scontato che siamo tutti d'accordo sul fatto che non vogliamo dare ad ogni singola routine nel nostro programma VBA il proprio gestore di errori. (Vedi il mio ragionamento qui: VBA Error "Bubble Up")

Se alcune routine interne non hanno i loro gestori di errori, poi quando ci facciamo cattura un errore, tutto quello che sappiamo è che è accaduto nella routine con il gestore degli errori che licenziato o in una routine da qualche parte più in profondità nella pila di chiamate. Quindi il problema come ho capito è in realtà uno dei tracciamento dell'esecuzione del nostro programma. Tracciare l'inserimento di routine è ovviamente facile. Ma il tracciamento dell'uscita può essere davvero piuttosto complicato. Ad esempio, potrebbe esserci un errore che viene generato!

L'approccio RAII ci consente di utilizzare il comportamento naturale della gestione della vita degli oggetti VBA per riconoscere quando siamo usciti da una routine, tramite un 'Exit', 'End' o un errore. Il mio esempio di giocattolo ha lo scopo di illustrare il concetto.Il vero "tracciante" nel mio quadro piccolo VBA è certamente più complesso, ma fa anche di più:

Private Sub Class_Terminate() 
    If unhandledErr_() Then 
     Call debugTraceException(callID_, "Err unhandled on exit: " & fmtCurrentErr()) 
    End If 

    If sendEntryExit_ Then 
     Select Case exitTraceStatus_ 
      Case EXIT_UNTRACED 
       Call debugTraceExitImplicit(callID_) 
      Case EXIT_NO_RETVAL 
       Call debugTraceExitExplicit(callID_) 
      Case EXIT_WITH_RETVAL 
       Call debugTraceExitExplicit(callID_, retval_) 
      Case Else 
       Call debugBadAssumption(callID_, "unrecognized exit trace status") 
     End Select 
    End If 
End Sub 

ma il suo utilizzo è ancora piuttosto semplice, ed è pari a meno boilerplate rispetto all'approccio "EH in ogni routine" in ogni caso:

Public Function apply(functID As String, seqOfArgs) 
    Const PROC As String = "apply" 
    Dim dbg As FW_Dbg: Set dbg = mkDbg(MODL_, PROC, functID, seqOfArgs) 

... 

generando automaticamente il testo standard è facile, anche se in realtà ho digitarlo e poi controllare automaticamente per fare i nomi sicuri di routine/arg corrispondono come parte dei miei test.

+0

Bello! char char – Fionnuala

+1

Mi sembra una soluzione terribilmente complicata a un problema relativamente semplice. –

+0

@ David-W-Fenton, non sono sicuro che sia davvero così semplice. Vedi la mia risposta modificata per perché suggerisco questo approccio. Sarei interessato a sentire il tuo metodo. – jtolle

5

Uso il pulsante di gestione degli errori all'interno del codice gratuito MZTools for VBA. Aggiunge automaticamente le righe di codice insieme al nome di sotto/funzione. Ora se si rinomina il sotto/funzione, è necessario ricordare di cambiare il codice.

MZTools ha anche molte belle funzioni incorporate. Ad esempio una schermata di ricerca migliorata e il migliore di tutti è un pulsante che mostra tutti i punti in cui viene chiamato questo sub/funzione.

+0

Dando uno sguardo veloce a MZTools, una o due funzionalità mi sembrano utili, grazie Tony. – maxhugen

+1

Solo uno o due?

+1

Beh, ho usato MZ Tools per un anno ... quindi Tony, ora sto usando più di 1 o 2 funzioni! Come programmatore di Access a lungo termine (con il suo set di buone/cattive pratiche), MZT è diventato un accessorio 'must have' :) Grazie! – maxhugen

2

vbWatchdog è una soluzione commerciale al problema. Ha un prezzo molto ragionevole per le sue capacità. Tra le altre caratteristiche offre pieno accesso allo stack di chiamate VBA. Non conosco altri prodotti che facciano questo (e ho guardato).

Ci sono molte altre funzionalità tra cui finestre di dialogo di ispezione variabili e personalizzate, ma l'accesso alla traccia dello stack da solo vale il prezzo di ammissione.

NOTA: non sono in alcun modo affiliato con il prodotto tranne che sono un utente estremamente soddisfatto.

0

Il codice è brutto ma funziona. Questo esempio aggiungerà codice di gestione degli errori a ciascuna funzione che contiene anche una stringa con il nome della funzione.

Function AddErrorCode() 
    Set vbc = ThisWorkbook.VBProject.VBComponents("Module1") 
    For VarVBCLine = 1 To vbc.codemodule.CountOfLines + 1000 
     If UCase(vbc.codemodule.Lines(VarVBCLine, 1)) Like UCase("*Function *") And Not (UCase(vbc.codemodule.Lines(VarVBCLine, 1)) Like UCase("*Function FunctionReThrowError*")) Then 
      If Not (vbc.codemodule.Lines(VarVBCLine + 1, 1) Like "*Dim VarFunctionName As String*") Then 
        vbc.codemodule.InsertLines VarVBCLine + 1, "Dim VarFunctionName as String" 
        vbc.codemodule.InsertLines VarVBCLine + 2, "VarFunctionName = """ & Trim(Mid(vbc.codemodule.Lines(VarVBCLine, 1), InStr(1, vbc.codemodule.Lines(VarVBCLine, 1), "Function") + Len("Function"), Len(vbc.codemodule.Lines(VarVBCLine, 1)))) & """" 
        VarVBCLine = VarVBCLine + 3 
      End If 
     End If 
     If UCase(vbc.codemodule.Lines(VarVBCLine, 1)) Like UCase("*End Function*") Then 
      If Not (vbc.codemodule.Lines(VarVBCLine - 1, 1) Like "*Call FunctionReThrowError(Err, VarFunctionName)*") And Not (UCase(vbc.codemodule.Lines(VarVBCLine - 1, 1)) Like UCase("*Err.Raise*")) Then 
       vbc.codemodule.InsertLines VarVBCLine, "ErrHandler:" 
       vbc.codemodule.InsertLines VarVBCLine + 1, "Call FunctionReThrowError(Err, VarFunctionName)" 
       VarVBCLine = VarVBCLine + 2 
      End If 
     End If 
    Next VarVBCLine 
    If Not (vbc.codemodule.Lines(1, 1) Like "*Function FunctionReThrowError(ByVal objError As ErrObject, PasFunctionName)*") Then 
     vbc.codemodule.InsertLines 1, "Function FunctionReThrowError(ByVal objError As ErrObject, PasFunctionName)" 
     vbc.codemodule.InsertLines 2, "Debug.Print PasFunctionName & objError.Description" 
     vbc.codemodule.InsertLines 3, "Err.Raise objError.Number, objError.Source, objError.Description, objError.HelpFile, objError.HelpContext" 
     vbc.codemodule.InsertLines 4, "End Function" 
    End If 
End Function 
1

Questo funziona per me. Sono il 2010.

ErrorHandler: 
    Dim procName As String 
    procName = Application.VBE.ActiveCodePane.CodeModule.ProcOfLine(Application.VBE.ActiveCodePane.TopLine, 0) 
    MyErrorHandler err, Me.Name, getUserID(), procName 
    Resume Exithere 
+0

Ho finito con l'utilizzo del componente aggiuntivo MZ-Tools (che consiglio vivamente), che può inserire automaticamente il mio codice di gestione degli errori originale in qualsiasi funzione/sotto - come da commenti Tony Toews. – maxhugen

+0

È utile, ma potrebbe essere difficile in quanto "Application.VBE.ActiveCodePane.TopLine" restituisce il numero di riga della linea nella parte superiore del riquadro del codice. Quindi, se si è in modalità di debug, il procName può essere cambiato con la procedura effettiva. E invece di 'Me.Name' dovresti usare direttamente 'Application.VBE.ActiveCodePane.CodeModule'. –

-3

Seriamente? Perché gli sviluppatori continuano a risolvere lo stesso problema ancora e ancora? Invia ottenere il nome della procedura in oggetto Err utilizzando Err.Raise ...

Per il passaggio parametro Source in:

Me.Name & "." & Application.VBE.ActiveCodePane.CodeModule.ProcOfLine(Application.VBE.ActiveCodePane.TopLine, 0) 

io so che non è la più breve uno di linea, ma se non si può permettere un prodotto commerciale per migliorare l'IDE VBA o se, come molti di noi, sono limitati a lavorare in un ambiente bloccato, questa è la soluzione più semplice.

Problemi correlati