21

我在 ASP.net、c# 中开发了一个 Web 服务,并托管在 IIS 上,供 vba 客户端使用。下载了 Office 2003 Web Services 2.01 工具包后,我在成功创建所需的代理类时遇到了问题(许多用户在线记录),并决定改为创建一个 .net dll 库。我已经创建了库,它引用了 Web 服务并将其方法之一公开给 c# 中的公共函数。

我现在有三个问题:

  1. 如何在 VBA 中引用 dll 类?我试图转到工具-> 参考并浏览到 dll 位置,但我收到错误“无法添加对指定文件的引用”。磁盘上是否有我必须复制 .dll 的特定位置?

  2. 我还可以将 dll.config 文件复制到 dll 文件旁边,以便在那里有端点 url 吗?

  3. 由于调用的方法是接受一个对象(由各种成员和几个 List<> 成员组成,这些如何在 VBA 代码中实现?

4

1 回答 1

35

您需要为您的程序集 (DLL) 创建一个可调用 COM 的包装器 (CCW)。.NET 互操作性是一个相当深入的话题,但它相对容易实现。

首先,您需要确保您的整个程序集都注册了 COM 互操作。您可以通过选中“注册 COM 互操作”在 Visual Studio 的“构建”选项卡上执行此操作。其次,您应该在所有类中包含 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。

于 2013-11-13T13:25:31.743 回答