2012-03-12 48 views
7

Tôi có một chương trình C# mở một số tệp Microsoft Access và thực hiện các hàm từ bên trong mỗi tệp.Làm thế nào tôi có thể nắm bắt lỗi gỡ lỗi Microsoft Access VBA từ mã C# của tôi?

Về cơ bản, mã trông giống như sau:

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); 

Tuy nhiên, khi một lỗi debug xảy ra trong mã VBA, tôi muốn bẫy nó trong C# chương trình của tôi.

Vui lòng không trả lời: "bẫy lỗi trong chương trình VBA của bạn". Vì những lý do mà tôi sẽ không tham gia, điều này là không thể.

Một phương pháp mà tôi đã sử dụng trong quá khứ là để có một chuỗi liên tục theo dõi cho một xử lý cho bất kỳ cửa sổ gỡ lỗi Visual Basic (chức năng FindWindowEx Win32 trả về một giá trị khác không). Tôi không thích phương pháp này, và không muốn tiếp tục sử dụng nó.

Tôi tìm thấy this thread, áp dụng cho Microsoft Excel. Về bản chất, nó sử dụng chức năng Microsoft.VisualBasic.CallByName(), dường như có thể bị mắc kẹt trong một khối try/catch, mà không có sự tương tác của người dùng. Tuy nhiên, tôi đã không thể có được điều này để làm việc với Microsoft Access - chủ yếu là vì tôi không thể tìm ra cách gọi hàm/phụ bằng cách sử dụng lệnh này.

Mọi đề xuất sẽ được đánh giá chân thành!

Edit: Như tôi đã đề cập trong một trong các câu trả lời dưới đây, tôi đã cố gắng gói các Eval() trong một khối try/catch và C# chương trình của tôi dường như bỏ qua nó, cho đến khi người dùng chạm "Kết thúc" nút trên Hộp thoại lỗi "Microsoft Visual Basic". Tôi không muốn bất kỳ sự tương tác người dùng nào, mà là muốn bẫy lỗi VBA để xử lý trong chương trình C# của tôi.

Trả lời

4

Cập nhật: Đối với một số lý do, mã trước đây tôi đã đăng đã chỉ làm việc khi các định dạng tập tin truy cập là năm 2000. Tôi đã xác nhận rằng mã mới này cũng làm việc với Access 2002 và 2010 tập tin.

Mã:

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(); 
      } 
     } 
    } 
} 

Các chi tiết:

Theo the thread I had linked to bởi Mike Rosenblum, CallByName() có thể thực thi mã văn phòng trong C#, và có thể ngoại lệ VBA bẫy (Application.Run()Application.Eval() dường như chỉ bị bắt sau khi người dùng tương tác với cửa sổ gỡ lỗi). Vấn đề là CallByName() yêu cầu một đối tượng [instantiated] để gọi một phương thức. Theo mặc định, Excel có đối tượng ThisWorkbook, được khởi tạo khi mở sổ làm việc. Truy cập không có một đối tượng tương tự có thể truy cập, theo như tôi biết.

A subsequent post on the same thread đề xuất thêm mã động vào sổ làm việc Excel để cho phép gọi các phương thức trong mô-đun chuẩn. Làm như vậy trên ThisWorkbook là tương đối tầm thường, bởi vì ThisWorkbook có một mã phía sau và được tự động khởi tạo. Nhưng làm thế nào chúng ta có thể làm điều này trong Access?

Các giải pháp kết hợp hai kỹ thuật trên theo cách sau:

  1. Programatically tạo ra một mô-đun lớp tạm thời mới trong file truy cập mục tiêu, mà để gọi hàm mục tiêu trong cơ sở dữ liệu Access. Lưu ý rằng thuộc tính Instancing của lớp phải được đặt thành 2 - PublicNotCreatable. Điều này có nghĩa là lớp học không thể tạo ra bên ngoài dự án đó, nhưng nó có thể truy cập công khai.
  2. Nối một mô-đun chuẩn mới vào tệp Access đích và tạo một hàm công khai để khởi tạo lớp và trả về nó.
  3. Nhận tham chiếu đến đối tượng trong mã C# của bạn, bằng cách gọi mã VBA ở bước (2). Điều này có thể được thực hiện bằng cách sử dụng Application.Run của Access interop().
  4. Gọi phương thức trên đối tượng từ (3) bằng cách sử dụng CallByName-- gọi phương thức này trong mô-đun chuẩn và có thể được chuyển nhượng.
  5. Khi bạn đóng cơ sở dữ liệu, bạn gọi Application.Quit() với acQuitSaveNone, vì vậy không có mã VBA nào bạn vừa tạo được lưu.

Để lấy mô tả lỗi VBA, sử dụng "e.Message", trong đó "e" là đối tượng COMException.

Hãy chắc chắn rằng bạn thêm các tài liệu tham khảo NET sau vào dự án # C:

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

Tôi không biết nếu đó là phương pháp tốt nhất, nhưng bạn sẽ có thể bắt COMException trong khối thử/nắm bắt và kiểm tra ngoại lệ để xem chính xác những gì đã được ném. Tôi làm điều này bằng cách sử dụng interop Excel.

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

Tôi đã thử một try/catch xung quanh phương pháp Eval, và nó dường như hoàn toàn bỏ qua nó. Bạn đang sử dụng Eval để gọi các hàm Excel của bạn? – transistor1

+1

Không, tôi không. Tôi đã nghĩ rằng interop sẽ nắm bắt nó và bong bóng nó lên đến người tiêu dùng của nó. – squillman

+0

Vì vậy, những gì bạn sử dụng bên trong phương pháp 'DoSomething()' của bạn để gọi các hàm VBA từ bên trong sổ làm việc Excel của bạn? Tôi hy vọng tôi đang thiếu một cái gì đó hiển nhiên! – transistor1

Các vấn đề liên quan