您需要为您的程序集(DLL)制作一个可调用的COM包装器(CCW)。 .NET互操作性是一个相当深入的话题,但很容易入门。
首先,您需要确保整个程序集都已注册为COM互操作。您可以在Visual Studio的“生成”选项卡中勾选“注册为COM互操作”来完成此操作。其次,您应该在所有类中包含System.Runtime.InteropServices:
using System.Runtime.InteropServices
接下来,您应该使用[Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
属性来装饰您想要公开的所有类。这将使得您可以在 VBA 编辑器内正确地访问类成员并使用智能感知功能。
您需要一个入口点——即一个主类,该类应该有一个没有参数的公共构造函数。从该类中,您可以调用返回其他类实例的方法。这里有一个简单的示例:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace MyCCWTest
{
[Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
public class Main
{
public Widget GetWidget()
{
return new Widget();
}
}
[Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
public class Widget
{
public void SayMyName()
{
MessageBox.Show("Widget 123");
}
}
}
编译汇编代码后,您应该能够通过"工具 > 引用"来将其作为引用在VBA中引用:
之后,您就可以像这样访问您的主类和任何其他类:
Sub Test()
Dim main As MyCCWTest.main
Set main = New MyCCWTest.main
Dim myWidget As MyCCWTest.Widget
Set myWidget = main.GetWidget
myWidget.SayMyName
End Sub
关于List<>的问题,COM 对泛型一无所知,因此不支持。实际上,在 CCW 中使用数组甚至是一个棘手的问题。根据我的经验,最简单的方法是创建自己的集合类。使用上面的例子,我可以创建一个 WidgetCollection 类。这是一个包含 WidgetCollection 类的稍作修改的示例项目:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace MyCCWTest
{
[Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
public class Main
{
private WidgetCollection myWidgets = new WidgetCollection();
public Main()
{
myWidgets.Add(new Widget("Bob"));
myWidgets.Add(new Widget("John"));
myWidgets.Add(new Widget("Mary"));
}
public WidgetCollection MyWidgets
{
get
{
return myWidgets;
}
}
}
[Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
public class Widget
{
private string myName;
public Widget(string myName)
{
this.myName = myName;
}
public void SayMyName()
{
MessageBox.Show(myName);
}
}
[Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
public class WidgetCollection : IEnumerable
{
private List<Widget> widgets = new List<Widget>();
public IEnumerator GetEnumerator()
{
return widgets.GetEnumerator();
}
public Widget this[int index]
{
get
{
return widgets[index];
}
}
public int Count
{
get
{
return widgets.Count;
}
}
public void Add(Widget item)
{
widgets.Add(item);
}
public void Remove(Widget item)
{
widgets.Remove(item);
}
}
}
你可以在VBA中像这样使用它:
Sub Test()
Dim main As MyCCWTest.main
Set main = New MyCCWTest.main
Dim singleWidget As MyCCWTest.Widget
For Each singleWidget In main.myWidgets
singleWidget.SayMyName
Next
End Sub
注意:我已经在新项目中包含了System.Collections;
,这样我的WidgetCollection类就可以实现IEnumerable。
Call
关键字时,VBA 中的参数需要括号。如果在调用不返回值的方法时(且没有使用Call
),你使用括号,VBA 将把括号中的内容作为表达式进行计算,并将该计算结果传递给该方法。通常这会导致“类型不匹配”错误。 - Tim Williams