Обновление: По какой-то причине предыдущий код, который я разместил, работал, только когда формат файла 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 () требует объекта [instantiated] для вызова метода. По умолчанию в Excel имеется объект ThisWorkbook
, который создается при открытии книги. Насколько мне известно, у Access нет похожего объекта, который доступен.
Последующее сообщение в той же теме предлагает динамически добавлять код в книгу Excel, чтобы разрешить вызов методов в стандартных модулях. Выполнение этого на ThisWorkbook
является относительно тривиальным, потому что ThisWorkbook имеет программный код и создается автоматически. Но как мы можем сделать это в Access?
Решение объединяет две вышеописанные методики следующим образом:
- Программно создайте новый модуль временного класса в целевом файле Access, с помощью которого можно вызвать целевую функцию в базе данных Access. Помните, что для свойства
Instancing
класса должно быть установлено значение 2 - PublicNotCreatable
. Это означает, что класс нельзя создать за пределами этого проекта, но он доступен публично.
- Добавьте новый стандартный модуль к целевому файлу Access и создайте публичную функцию для создания экземпляра класса и его возврата.
- Получите ссылку на объект в вашем коде C #, вызвав код VBA на шаге (2). Это можно сделать с помощью приложения Access interop's Application.Run ().
- Вызвать метод объекта из (3), используя CallByName--, который вызывает метод в стандартном модуле и является trappable.
- Когда вы закрываете базу данных, вы вызываете
Application.Quit()
с acQuitSaveNone
, поэтому ни один из только что созданного вами кода VBA не сохраняется.
Чтобы получить описание ошибки VBA, используйте «e.Message», где «e» - это объект COMException.
Убедитесь, что вы добавили следующие ссылки .NET в ваш проект C #:
Microsoft.Office.Interop.Access
Microsoft.Vbe.Interop
Microsoft.VisualBasic