2012-03-12 6 views
7

Ho un programma C# che apre diversi file di Microsoft Access ed esegue funzioni all'interno di ciascuna di esse.Come posso acquisire un errore di debug VBA di Microsoft Access dal mio codice C#?

In sostanza, il codice simile a questa:

Microsoft.Office.Interop.Access.Application app = 
    new Microsoft.Office.Interop.Access.Application(); 

app.Visible = true; 
app.OpenCurrentDatabase(accessFileFullPath, false, ""); 

//Call the function 
app.Eval(function); 

Tuttavia, quando si verifica un errore di debug nel codice VBA, mi piacerebbe intrappolare nel mio programma C#.

risposta: "intercettare l'errore nel programma VBA". Per ragioni a cui non entrerò, questo non è possibile.

Un metodo che ho utilizzato in passato consiste nel fare in modo che un thread controlli in modo intermittente un handle per qualsiasi finestra di debug di Visual Basic (la funzione Win32 FindWindowEx restituisce un valore diverso da zero). Non mi piace questo metodo e non voglio continuare a usarlo.

Ho trovato this thread, che si applica a Microsoft Excel. In sostanza, utilizza la funzione Microsoft.VisualBasic.CallByName(), che a quanto pare può essere intrappolata in un blocco try/catch, senza l'interazione dell'utente. Tuttavia, non sono stato in grado di farlo funzionare con Microsoft Access-- principalmente perché non riesco a capire come chiamare la funzione/sub utilizzando questo comando.

Qualsiasi suggerimento sarebbe apprezzato sinceramente!

Edit: Come ho già detto in una delle risposte qui sotto, ho provato avvolgendo la Eval() in un blocco try/catch e il mio programma C# sembra ignorare che, fino a quando un utente preme il pulsante "Fine" sulla Finestra di dialogo di errore "Microsoft Visual Basic". Non voglio alcuna interazione con l'utente, ma piuttosto voglio intrappolare l'errore VBA per la gestione nel mio programma C#.

risposta

4

Update: Per qualche ragione, il codice precedente avevo postato aveva lavorato solo quando il formato di file di Access 2000. Era mi hanno confermato che questo nuovo codice funziona anche con Access 2002 e file 2010.

Il codice:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 
using VBA = Microsoft.Vbe.Interop; 

namespace CaptureVBAErrorsTest 
{ 
    class CaptureVBAErrors 
    { 
     public void runApp(string databaseName, string function) 
     { 
      VBA.VBComponent f = null; 
      VBA.VBComponent f2 = null; 
      Microsoft.Office.Interop.Access.Application app = null; 
      object Missing = System.Reflection.Missing.Value; 
      Object tempObject = null; 

      try 
      { 
       app = new Microsoft.Office.Interop.Access.Application(); 
       app.Visible = true; 
       app.OpenCurrentDatabase(databaseName, false, ""); 

       //Step 1: Programatically create a new temporary class module in the target Access file, with which to call the target function in the Access database 

       //Create a Guid to append to the object name, so that in case the temporary class and module somehow get "stuck", 
       //the temp objects won't interfere with other objects each other (if there are multiples). 
       string tempGuid = Guid.NewGuid().ToString("N"); 

       f = app.VBE.ActiveVBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_ClassModule); 

       //We must set the Instancing to 2-PublicNotCreatable 
       f.Properties.Item("Instancing").Value = 2; 
       f.Name = "TEMP_CLASS_" + tempGuid; 
       f.CodeModule.AddFromString(
        "Public Sub TempClassCall()\r\n" + 
        " Call " + function + "\r\n" + 
        "End Sub\r\n"); 

       //Step 2: Append a new standard module to the target Access file, and create a public function to instantiate the class and return it. 
       f2 = app.VBE.ActiveVBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_StdModule); 
       f2.Name = "TEMP_MODULE_" + tempGuid 
       f2.CodeModule.AddFromString(string.Format(
        "Public Function instantiateTempClass_{0}() As Object\r\n" + 
        " Set instantiateTempClass_{0} = New TEMP_CLASS_{0}\r\n" + 
        "End Function" 
        ,tempGuid)); 

       //Step 3: Get a reference to a new TEMP_CLASS_* object 
       tempObject = app.Run("instantiateTempClass_" + tempGuid, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing); 

       //Step 4: Call the method on the TEMP_CLASS_* object. 
       Microsoft.VisualBasic.Interaction.CallByName(tempObject, "TempClassCall", Microsoft.VisualBasic.CallType.Method); 
      } 
      catch (COMException e) 
      { 
       MessageBox.Show("A VBA Exception occurred in file:" + e.Message); 
      } 
      catch (Exception e) 
      { 
       MessageBox.Show("A general exception has occurred: " + e.StackTrace.ToString()); 
      } 
      finally 
      { 
       //Clean up 
       if (f != null) 
       { 
        app.VBE.ActiveVBProject.VBComponents.Remove(f); 
        Marshal.FinalReleaseComObject(f); 
       } 

       if (f2 != null) 
       { 
        app.VBE.ActiveVBProject.VBComponents.Remove(f2); 
        Marshal.FinalReleaseComObject(f2); 
       } 

       if (tempObject != null) Marshal.FinalReleaseComObject(tempObject); 

       if (app != null) 
       { 
        //Step 5: When you close the database, you call Application.Quit() with acQuitSaveNone, so none of the VBA code you just created gets saved. 
        app.Quit(Microsoft.Office.Interop.Access.AcQuitOption.acQuitSaveNone); 
        Marshal.FinalReleaseComObject(app); 
       } 

       GC.Collect(); 
       GC.WaitForPendingFinalizers(); 
      } 
     } 
    } 
} 

I Dettagli:

Secondo the thread I had linked to da Mike Rosenblum, CallByName() può eseguire codice Ufficio in C#, e può eccezioni VBA trap (Application.Run() e Application.Eval() sembra che vengano catturati solo dopo che l'utente interagisce con la finestra di debug). Il problema è che CallByName() richiede un oggetto [istanziato] per chiamare un metodo. Per impostazione predefinita, Excel ha l'oggetto ThisWorkbook, che viene istanziato all'apertura di una cartella di lavoro. L'accesso non ha un oggetto simile che sia accessibile, per quanto ne so.

A subsequent post on the same thread suggerisce di aggiungere il codice dinamicamente alla cartella di lavoro di Excel per consentire la chiamata di metodi in moduli standard. Farlo su ThisWorkbook è relativamente banale, perché ThisWorkbook ha un code-behind e viene istanziato automaticamente. Ma come possiamo farlo in Access?

La soluzione combina le due tecniche sopra nel seguente modo:

  1. creare Programatically un nuovo temporanea modulo classe nel file di accesso di destinazione, con cui chiamare la funzione di destinazione nel database di Access. Tenere presente che la proprietà Instancing della classe deve essere impostata su 2 - PublicNotCreatable. Ciò significa che la classe non è creabile al di fuori di quel progetto, ma è accessibile pubblicamente.
  2. Aggiungere un nuovo modulo standard al file di destinazione e creare una funzione pubblica per istanziare la classe e restituirla.
  3. Ottieni un riferimento all'oggetto nel tuo codice C#, chiamando il codice VBA al punto (2). Questo può essere fatto usando Application.Run() di Access Access.
  4. Chiama il metodo sull'oggetto da (3) utilizzando CallByName-- che chiama il metodo nel modulo standard ed è intercettabile.
  5. Quando si chiude il database, si chiama Application.Quit() con acQuitSaveNone, quindi nessuno del codice VBA appena creato viene salvato.

Per ottenere la descrizione dell'errore VBA, utilizzare "e.Message", dove "e" è l'oggetto COMException.

Assicurati di aggiungere i seguenti riferimenti .NET al progetto C#:

Microsoft.Office.Interop.Access 
Microsoft.Vbe.Interop 
Microsoft.VisualBasic 
3

Non so se è il metodo migliore, ma dovresti essere in grado di catturare COMException in un blocco try/catch e ispezionare l'eccezione per vedere cosa è stato lanciato. Lo faccio utilizzando l'interoperabilità di Excel.

try { 
    DoSomething(); 
} catch (COMException ex) { 
    Console.WriteLine ex.ErrorCode.ToString(); 
    Console.WriteLine ex.Message; 
} 
+0

ho cercato un try/catch attorno al metodo Eval, e sembra ignorare completamente. Stai usando Eval per chiamare le tue funzioni di Excel? – transistor1

+1

No, non lo sono. Avrei pensato che l'interop lo avrebbe catturato e fatto saltare in aria al suo consumatore. – squillman

+0

Quindi, che cosa usi all'interno del tuo metodo 'DoSomething()' per chiamare le funzioni VBA dalla cartella di lavoro di Excel? Spero di perdere qualcosa di ovvio! – transistor1

Problemi correlati