如何从C#代码中捕获Microsoft Access VBA调试错误?

tra*_*or1 7 c# ms-access vba office-interop

我有一个C#程序,它打开几个Microsoft Access文件,并从每个文件中执行功能.

本质上,代码看起来像这样:

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);
Run Code Online (Sandbox Code Playgroud)

但是,当VBA代码中发生调试错误时,我想将其捕获到我的C#程序中.

不要回答:"在您的VBA程序中捕获错误".由于我不会涉及的原因,这是不可能的.

我过去使用的一种方法是让一个线程间歇性地监视任何Visual Basic Debug窗口的句柄(FindWindowEx Win32函数返回非零值).我不喜欢这种方法,也不想继续使用它.

我找到了这个适用于Microsoft Excel的线程.从本质上讲,它使用的Microsoft.VisualBasic.CallByName()功能显然可以被困在try/catch块中,而无需用户交互.但是,我无法使用Microsoft Access - 主要是因为我无法弄清楚如何使用此命令调用函数/ sub.

任何建议都将真诚地感谢!

编辑:正如我在下面的一个答案中提到的,我尝试Eval()在try/catch块中包装,我的C#程序似乎忽略它,直到用户点击"Microsoft Visual Basic"错误对话框上的"结束"按钮.我不希望任何用户交互,而是想要捕获VBA错误以便在我的C#程序中处理.

tra*_*or1 5

更新:出于某种原因,我发布的上一代码仅在Access文件格式为2000时才有效.我已确认此新代码也适用于Access 2002和2010文件.

代码:

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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

细节:

我已经挂线麦克·布鲁姆,CallByName()可以在C#中执行办公室的代码,并可以捕获VBA异常(Application.Run()Application.Eval()似乎是用户与调试窗口交互后,只抓到).问题是CallByName()需要一个[实例化]对象来调用方法.默认情况下,Excel具有该ThisWorkbook对象,该对象在打开工作簿时实例化.据我所知,Access没有类似的可访问对象.

同一线程上的后续帖子建议将代码动态添加到Excel工作簿,以允许调用标准模块中的方法.这样做ThisWorkbook是相对微不足道的,因为ThisWorkbook具有代码隐藏功能并自动实例化.但是我们怎样才能在Access中做到这一点?

该解决方案以下列方式结合了上述两种技术:

  1. 以编程方式在目标Access文件中创建一个新的临时类模块,用于在Access数据库中调用目标函数.请记住,Instancing必须将类的属性设置为2 - PublicNotCreatable.这意味着该类不能在该项目之外创建,但可以公开访问.
  2. 将新标准模块附加到目标Access文件,并创建一个公共函数来实例化该类并将其返回.
  3. 通过在步骤(2)中调用VBA代码来获取C#代码中对象的引用.这可以使用Access interop的Application.Run()来完成.
  4. 使用CallByName--在(3)中调用对象上的方法,CallByName--调用标准模块中的方法,并且是可捕获的.
  5. 当您关闭数据库时,请Application.Quit()使用acQuitSaveNone,因此您刚刚创建的VBA代码都不会被保存.

要获取VBA错误描述,请使用"e.Message",其中"e"是COMException对象.

确保将以下.NET引用添加到C#项目中:

Microsoft.Office.Interop.Access
Microsoft.Vbe.Interop
Microsoft.VisualBasic
Run Code Online (Sandbox Code Playgroud)