簡體   English   中英

如何從C#代碼中捕獲Microsoft Access VBA調試錯誤?

[英]How can I capture a Microsoft Access VBA debug error from my C# code?

我有一個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);

但是,當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#程序中處理。

更新:出於某種原因,我發布的上一個代碼僅在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();
            }
        }
    }
}

細節:

根據Mike Rosenblum 鏈接的線程 ,CallByName()可以在C#中執行Office代碼,並且可以捕獲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. 當您關閉數據庫時,使用acQuitSaveNone調用Application.Quit() ,因此您剛剛創建的VBA代碼都不會被保存。

要獲取VBA錯誤描述,請使用“e.Message”,其中“e”是COMException對象。

確保將以下.NET引用添加到C#項目中:

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

我不知道它是否是最好的方法,但你應該能夠在try / catch塊中捕獲COMException並檢查異常以查看究竟拋出了什么。 我這樣做是使用Excel互操作。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM