如何从Excel中访问Excel VBA中的类模块?

我有一个类加载模块的Excel加载项。 我想在C#中实例化类模块并调用一个方法。 我怎么做?

如果您确实需要访问该类的实例,则可以执行以下操作:

  • 为您希望从VBA类公开的COM接口生成一个types库(例如IMyComInterface)

  • 在您的VBA项目中添加对此types库的引用

  • 在VBA类模块中实现接口 – 例如MyVbaClass(使用Implements关键字):

    Option Explicit Implements IMyComInterface Private Sub IMyComInterface_SomeMethod(...) ... End Sub ... 
  • 在C#项目中引用相同的types库

  • 使用接受对VBA接口实例的引用的方法创build一个ComVisible C#类。 就像是:

     public class MyVbaLoader { public IMyComInterface MyComInterface { get { return myComInterface; } set { myComInterface = value; } } } 
  • 在VBA标准模块中写入一个“工厂”方法,将一个对象作为ByRef参数。 该对象应该假设作为parameter passing的对象具有“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代码中有一个“工作簿”,这个代码看起来像这样:

     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 // ... 

正如你所看到的,这相当复杂。 如果没有更多的细节来解决问题,那么很难说这个复杂性是否合理,或者是否有更简单的解决scheme。

如果上述不明确,让我知道,我会尽力澄清。

基本上,你不能从使用Application.Run的C#代码调用的VBAmacros中返回一个值,所以你不得不通过一个具有可以从VBA调用的方法或属性的值传递一个对象来设置实例。

VBA类模块只有两种实例模式:私有模式和公共不可创build模式。 所以,你甚至不能在另一个VB(A)项目中实例化它们,更不用说C#了。

然而,没有什么可以阻止你拥有一个标准模块,充当一个类工厂。 所以,如果你的类模块是Foo那么你可以在一个名为NewFoo的标准模块中使用一个方法,为你实例化一个新的Foo并将其返回给调用者。 Foo对象显然必须是公共不可创造的。

[你的NewFoo方法可以带参数,所以你可以模拟参数化的构造函数,这在VBA中是不可用的。]

编辑 :如何从C#中调用VBA函数(在一个标准模块中)的细节,并获得使用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; } } 

请注意,您可以省略所有尝试…捕获的东西 – 它所做的是处理macros不存在的特定错误,并在这种情况下引发更有意义的exception。

从函数返回的object可以转换为你需要的任何types。 例如:

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

如果你正在做很多这样的调用,显然你可以通过创build包装器方法来为你打电话来隐藏丑陋的细节。

请注意,我已经从内存中input了大部分内容,所以我完全期望有语法,逻辑甚至事实错误:-)