2012-03-12 17 views
7

Tengo un programa C# que abre varios archivos de Microsoft Access y ejecuta funciones desde dentro de cada uno.¿Cómo puedo capturar un error de depuración de Microsoft Access VBA desde mi código de C#?

En esencia, el código es como la siguiente:

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); 

Sin embargo, cuando se produce un error de depuración en el código VBA, me gustaría trampa en mi programa en C#.

Por favor, no lo hacen respuesta: "captura el error en su programa de VBA". Por razones que no entraré, esto no es posible.

Un método que he utilizado en el pasado es tener un subproceso monitor intermitente para un identificador a cualquier ventana de depuración de Visual Basic (la función FindWindowEx Win32 devuelve un valor distinto de cero). No me gusta este método y no quiero seguir usándolo.

Encontré this thread, que se aplica a Microsoft Excel. En esencia, usa la función Microsoft.VisualBasic.CallByName(), que aparentemente puede quedar atrapada en un bloque try/catch, sin interacción del usuario. Sin embargo, no he podido hacer que esto funcione con Microsoft Access, principalmente porque no puedo encontrar la manera de llamar a la función/sub utilizando este comando.

¡Cualquier sugerencia sería sinceramente apreciada!

Editar: Como ya he mencionado en una de las respuestas a continuación, he intentado envolver el Eval() en un bloque try/catch y mi C# programa parece hacer caso omiso de ella, hasta que un usuario pulsa el botón "End" en el Cuadro de diálogo de error "Microsoft Visual Basic". No quiero ninguna interacción del usuario, sino que quiero atrapar el error de VBA para su manejo en mi programa C#.

Respuesta

4

Actualización: Por alguna razón, el código anterior había publicado solamente había trabajado cuando el formato de archivo de Access fue 2000. He confirmado que este nuevo código también funciona con Access 2002 y 2010 archivos.

El código:

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(); 
      } 
     } 
    } 
} 

Los detalles:

Según the thread I had linked to por Mike Rosenblum, CallByName() puede ejecutar código Oficina en C#, y pueden excepciones VBA trampa (Application.Run() y Application.Eval() parece que solo se atrapa después de que el usuario interactúa con la ventana de depuración). El problema es que CallByName() requiere un objeto [instanciado] para invocar un método. De forma predeterminada, Excel tiene el objeto ThisWorkbook, que se crea una instancia al abrir un libro de trabajo. El acceso no tiene un objeto similar que sea accesible, hasta donde yo sé.

A subsequent post on the same thread sugiere agregar código dinámicamente al libro de Excel para permitir la llamada de métodos en módulos estándar. Hacerlo en ThisWorkbook es relativamente trivial, porque ThisWorkbook tiene un código subyacente y se crea una instancia automáticamente. Pero, ¿cómo podemos hacer esto en Access?

La solución combina las dos técnicas antes de la siguiente manera:

  1. Programatically crear un nuevo módulo de clase temporal en el archivo de acceso de destino, con el que llama a la función objetivo en la base de datos Access. Tenga en cuenta que la propiedad Instancing de la clase debe establecerse en 2 - PublicNotCreatable. Esto significa que la clase no se puede crear fuera de ese proyecto, pero es accesible públicamente.
  2. Agregue un nuevo módulo estándar al archivo de acceso de destino y cree una función pública para crear una instancia de la clase y devolverla.
  3. Obtenga una referencia al objeto en su código C#, llamando al código VBA en el paso (2). Esto se puede hacer usando la Aplicación.Run de Access interop().
  4. Llamar al método en el objeto desde (3) usando CallByName-- que llama al método en el módulo estándar, y es interceptable.
  5. Cuando cierra la base de datos, llama al Application.Quit() con acQuitSaveNone, por lo que ninguno de los códigos VBA que acaba de crear se guarda.

Para obtener la descripción del error de VBA, use "e.Message", donde "e" es el objeto COMException.

asegúrese de agregar las siguientes referencias .NET a su proyecto de C#:

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

No sé si es el mejor método, pero debería poder atrapar COMException en un bloque try/catch e inspeccionar la excepción para ver exactamente qué fue arrojado. Lo hago usando la interoperabilidad de Excel.

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

He probado un try/catch alrededor del método Eval, y parece ignorar por completo. ¿Estás utilizando Eval para llamar a tus funciones de Excel? – transistor1

+1

No, no lo soy. Pensé que la interoperabilidad lo atraparía y lo burbujearía sobre su consumidor. – squillman

+0

Entonces, ¿qué usa dentro de su método 'DoSomething()' para llamar a las funciones de VBA desde su libro de Excel? ¡Espero que me esté perdiendo algo obvio! – transistor1

Cuestiones relacionadas