更新: 由于某些原因,我之前发布的代码只能在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, "");
string tempGuid = Guid.NewGuid().ToString("N");
f = app.VBE.ActiveVBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_ClassModule);
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");
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));
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);
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
{
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)
{
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 中怎么做呢?
解决方案将上述两种技术结合起来:
- 在目标 Access 文件中编程创建一个新的临时类模块,以调用 Access 数据库中的目标函数。请记住,类的
Instancing
属性必须设置为 2 - PublicNotCreatable
。这意味着该类无法在该项目之外被创建,但是可以公开访问。
- 将一个新的标准模块附加到目标 Access 文件中,并创建一个公共函数以实例化类并返回它。
- 通过调用步骤(2)中的 VBA 代码,在 C# 代码中获取对象的引用。这可以使用 Access 互操作的 Application.Run() 来完成。
- 使用 CallByName 调用 (3) 中的对象上的方法——它调用标准模块中的方法,并且是可捕获的。
- 当你关闭数据库时,调用带有
acQuitSaveNone
的 Application.Quit()
,以便不保存你刚创建的任何 VBA 代码。
要获取 VBA 错误描述,请使用 "e.Message",其中 "e" 是 COMException 对象。
确保在 C# 项目中添加以下 .NET 引用:
Microsoft.Office.Interop.Access
Microsoft.Vbe.Interop
Microsoft.VisualBasic