2009-11-13 112 views

回答

3

如果你確實需要的情況下訪問類,你可以做到以下幾點:

  • 生成一個COM接口,你想從你的VBA類暴露一個類型庫(例如IMyComInterface)

  • 添加和借鑑e添加到VBA項目中的此類型庫

  • 實現VBA類模塊中的接口 - 例如, MyVbaClass(請使用implements關鍵字):

    Option Explicit 
    Implements IMyComInterface 
    
    Private Sub IMyComInterface_SomeMethod(...) 
        ... 
    End Sub 
    ... 
    
  • 參考同類型庫在C#項目

  • 創建與接受的VBA接口實例的引用的方法的標記有ComVisible特性的C#類。喜歡的東西:

    public class MyVbaLoader 
    { 
        public IMyComInterface MyComInterface 
        { 
         get { return myComInterface; } 
         set { myComInterface = value; } 
        } 
    } 
    
  • 寫VBA標準模塊中的「工廠」的方法,即採用一個對象作爲ByRef參數。該對象應該假定作爲參數傳遞的對象具有「MyComInterface」屬性,並且應該將此屬性設置爲VBA類MyClass的新實例。

    Public Sub MyFactoryMethod(MyVbaLoader As Object) 
        Dim objClass As MyVbaClass 
        Set objClass = New MyVbaClass 
        ... any initialization of objClass here ... 
    
        ' Pass a reference to the VBA class to the C# object MyVbaLoader 
        MyVbaLoader.MyComInterface = objClass 
    End Sub 
    
  • 從您的C#代碼調用工廠方法。假設你已經打開工作簿,並在你的VBA代碼有refence「工作簿」,代碼看起來類似:

    MyVbaLoader loader = new MyVbaLoader(); 
    workbook.Application.Run("MyModule.MyFactoryMethod", loader, Type.Missing, ... , Type.Missing); 
    // we now have a reference to the VBA class module in loader.MyComInterface 
    // ... 
    

正如你可以看到,這是相當複雜的。如果沒有更多細節來解決問題,那麼很難說這種複雜性是否合理,或者是否有更簡單的解決方案。

如果上述不明確,讓我知道,我會盡力澄清。

基本上,你不能從使用Application.Run從你的C#代碼調用的VBA宏中返回一個值,所以你必須求助於通過具有可以從VBA調用的方法或屬性的值傳遞一個對象到設置實例。

+0

很好的答案,但實際上你可以從VBA宏中返回一個值 - 請參閱我的答案瞭解詳細信息。這將避免你的其他方案中唯一難看的解決方案。 – 2009-11-14 09:38:03

+0

注意:必須將VBA類標記爲「PublicNotCreatable」,否則您將收到與HRESULT 0x800A0062相關的異常。 – robodude666 2016-08-05 23:39:30

2

VBA類模塊只有兩種實例化模式:private和public-not-creatable。所以,你甚至不能在另一個VB(A)項目中實例化它們,更不用說C#了。

但是,沒有什麼能阻止你擁有一個作爲類工廠的標準模塊。因此,如果您的班級模塊是Foo,那麼您可以在名爲NewFoo的標準模塊中使用一種方法,爲您實例化新的Foo並將其返回給調用者。顯然,Foo對象必須是公共不可創建的。

[你NewFoo方法可以帶參數,所以你可以模擬參數的構造函數,這是不可用VBA中]

編輯:關於如何調用VBA函數(標準模塊中)從細節C#並使用Application.Run獲取返回值。

private static object RunMacro(Excel.Application excelApp, string macroName, object[] parameters) 
{ 
    Type applicationType = excelApp.GetType(); 

    ArrayList arguments = new ArrayList(); 

    arguments.Add(macroName); 

    if (parameters != null) 
     arguments.AddRange(parameters); 

    try 
    { 
     return applicationType.InvokeMember("Run", BindingFlags.Default | BindingFlags.InvokeMethod, null, excelApp, arguments.ToArray()); 
    } 
    catch (TargetInvocationException ex) 
    { 
     COMException comException = ex.InnerException as COMException; 

     if (comException != null) 
     { 
      // These errors are raised by Excel if the macro does not exist 

      if ( (comException.ErrorCode == -2146827284) 
       || (comException.ErrorCode == 1004)) 
       throw new ApplicationException(string.Format("The macro '{0}' does not exist.", macroName), ex); 
     } 

     throw ex; 
    } 
} 

請注意,您可以省略所有的try ... catch東西 - 所有它做的是處理特定錯誤的宏不存在,提高在這種情況下一個更有意義的異常。

該函數返回的object可以轉換爲您需要的任何類型。例如:

object o = RunMacro(excelApp, "MyModule.MyFunc", new object[] { "param1", 2 }); 
if (o is string) 
{ 
    string s = (string) o; 
    Console.WriteLine(s); 
} 

假設函數實際上返回您的VBA定義的類對象的實例,那麼你可以調用對象的方法以同樣的方式,再次使用InvokeMember

object o = RunMacro(excelApp, "MyModule.MyFunc", new object[] { "param1", 2 }); 

// assume o is an instance of your class, and that it has a method called Test that takes no arguments 
o.GetType().InvokeMember("Run", BindingFlags.Default | BindingFlags.InvokeMethod, null, o, new string[] {"Test"}); 

如果你正在做很多這些調用,那麼顯然你可以通過創建包裝器方法來爲你打電話來隱藏醜陋的細節。

請注意,我已鍵入極本從內存,所以我完全相信那裏是語法,邏輯,甚至可能事實錯誤:-)

+0

但是您仍然沒有生成運行時可調用包裝器(RCW)的類型庫 – Joe 2009-11-13 23:41:29

+0

正確,但您可以直接使用InvokeMember調用該類的方法。不漂亮或不愉快,但仍然可能。如果實際上只有一個成員想打電話,那麼比你的(坦率地說優秀的)答案要求的所有連接要容易得多。 – 2009-11-14 09:09:42

+0

+1,我不知道你可以從Application.Run調用VBA函數並返回結果。我會重做我的答案以利用這一點。這給OP留下了兩個解決方案 - 從我的答案的VBA代碼中的類型庫實現接口,或者在響應中使用反射訪問VBA類成員。 – Joe 2009-11-14 10:58:01