Как я могу захватить ошибку отладки Microsoft Access VBA из моего кода на C #?

У меня есть программа на 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». По причинам, в которые я не буду входить, это невозможно.

Метод, который я использовал в прошлом, заключается в том, что stream обрабатывает прерывания в любом окне Visual Basic Debug (функция FindWindowEx Win32 возвращает ненулевое значение). Мне не нравится этот метод и я не хочу продолжать его использовать.

Я нашел этот stream , который относится к Microsoft Excel. По сути, он использует функцию Microsoft.VisualBasic.CallByName() , которая, по-видимому, может быть захвачена блоком try / catch без взаимодействия с пользователем. Однако мне не удалось заставить это работать с Microsoft Access – прежде всего потому, что я не могу понять, как вызвать функцию / юг с помощью этой команды.

Любые предложения были бы искренне оценены!

Изменить: Как я уже упоминал в одном из ответов ниже, я попытался обернуть 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(); } } } } 

Детали:

В соответствии с темой, которую я связал с Майком Розенблюмом , CallByName () может выполнять код Office на C # и может блокировать исключения VBA ( Application.Run() и Application.Eval() кажется, попадают только после того, как пользователь взаимодействует с отладочное окно). Проблема заключается в том, что CallByName () требует, чтобы объект [экземпляр] вызывал метод. По умолчанию Excel имеет объект ThisWorkbook , который создается при открытии книги. Насколько мне известно, у Access нет похожего объекта, который доступен.

Последующая публикация в том же streamе предлагает динамически добавлять код в книгу Excel, чтобы разрешить вызов методов в стандартных модулях. Выполнение этой ThisWorkbook относительно тривиально, потому что ThisWorkbook имеет код-позади и автоматически создается. Но как мы можем сделать это в Access?

Решение объединяет два метода выше следующим образом:

  1. Программно создайте новый модуль временных classов в целевом файле Access, с помощью которого можно вызвать целевую функцию в базе данных Access. Имейте в виду, что свойство Instancing classа должно быть равно 2 - PublicNotCreatable . Это означает, что class не создается вне этого проекта, но доступен для публики.
  2. Добавьте новый стандартный модуль в целевой файл Access и создайте общедоступную функцию для создания экземпляра classа и возврата его.
  3. Получите ссылку на объект в коде C #, вызвав код VBA на шаге (2). Это можно сделать с помощью приложения Access interop’s Application.Run ().
  4. Вызовите метод на объекте из (3), используя CallByName–, который вызывает метод в стандартном модуле, и он является доступным.
  5. Когда вы закрываете базу данных, вы вызываете Application.Quit() с acQuitSaveNone , поэтому ни один из созданного вами кода VBA не сохраняется.

Чтобы получить описание ошибки VBA, используйте «e.Message», где «e» – это объект COMException.

Убедитесь, что вы добавили следующие ссылки .NET к вашему проекту C #:

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

Я не знаю, является ли это лучшим методом, но вы должны уловить COMException в блоке try / catch и проверить исключение, чтобы увидеть, что именно было выбрано. Я делаю это с помощью взаимодействия Excel.

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