从VBA调用.net库方法

21

我使用ASP.net和c#开发了一个Web服务,并在IIS上进行了托管,这个服务将被一个VBA客户端调用。我下载了Office 2003 Web Services 2.01 Toolkit,但是在成功创建所需的代理类时遇到了问题(正如许多用户在线文档中所述),于是决定创建一个.net dll库。我已经创建了库,它引用了Web服务并将其方法之一暴露为C#中的公共函数。

现在我有三个问题:

  1. 我该如何在VBA中引用dll类?我尝试转到“工具”菜单->“引用”,并浏览到dll位置,但我得到“无法添加对指定文件的引用”的错误。我必须把.dll复制到特定位置吗?

  2. 我能否将dll.config文件复制到dll文件旁边,以便在那里拥有终结点URL?

  3. 由于要调用的方法接受一个对象(包含多个成员和一对List<>成员),这些在VBA代码中该如何实现?

1个回答

35

您需要为您的程序集(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中引用:

enter image description here 之后,您就可以像这样访问您的主类和任何其他类:

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。


1
VBA真的很古怪。我在我的系统上测试了它,当我执行这个操作时也遇到了类型不匹配的错误:“objWidgets.Add(objWidget)”,但是如果在前面加上关键字“Call”,它就可以工作:“Call objWidgets.Add(objWidget)”。就像我说的:古怪。希望这有所帮助! - rory.ap
9
针对上述评论,当你调用一个返回值(即一个函数)的方法或使用 Call 关键字时,VBA 中的参数需要括号。如果在调用不返回值的方法时(且没有使用 Call),你使用括号,VBA 将把括号中的内容作为表达式进行计算,并将该计算结果传递给该方法。通常这会导致“类型不匹配”错误。 - Tim Williams
1
@rory.ap 很棒的帖子。谢谢。 - Avrohom
2
@jimtollan -- 谢谢。听到这样的话真是令人欣慰。我花了很长时间才弄清楚所有这些并使其正常工作,所以我真的很高兴我已经帮助了许多面临同样问题的人。这就是SO的精髓所在。 - rory.ap
1
Rory - 我发现在使用Json.Net时IEnumerable不起作用,几乎让我垂头丧气,但是我在SO上找到了这个项目,它作为IEnumerable的热插拔:https://dev59.com/K5bfa4cB1Zd3GeqPvZGJ。现在我的集合类可以保存和读取json保存的数据了...只用了2天就走了很长的路 ;) - jim tollan
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接