【问题标题】:Calling a .net library method from vba从 vba 调用 .net 库方法
【发布时间】:2013-11-26 01:36:53
【问题描述】:

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

我现在有三个问题:

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

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

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

【问题讨论】:

    标签: .net vba com com-callable-wrapper


    【解决方案1】:

    您需要为您的程序集 (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。

    【讨论】:

    • VBA 真的很古怪。我在我的系统上测试了它,当我这样做时我也得到了类型不匹配错误:“objWidgets.Add(objWidget)”但是如果你把关键字“Call”放在它前面,它可以工作:“Call objWidgets.添加(objWidget)”。就像我说的:古怪。希望有帮助!
    • 对于阅读上述评论的任何人:只有在调用返回值的方法(即函数)或使用 Call 关键字时,VBA 中才需要使用括号括起来。如果在调用 not 返回值的方法时使用括号(并且您没有使用Call),VBA 将评估括号中包含的任何内容作为表达式,并传递结果对该方法的评估。这通常会导致“类型不匹配”错误。
    • @rory.ap 惊人的帖子。谢谢。
    • Rory - 我特地登录(在离开 SO 2 年后)投票赞成。这是一篇非常有用的帖子,对我来说来得正是时候,因为我正在实施一个解决方案,该解决方案通过办公室互操作使用 3rd 方应用程序来表现出色。现在,这让我可以在我的 .net 专业知识上聚会,以实现 excel 中的功能并将 vba 纯粹用作包装器......谢谢
    • @jimtollan -- 谢谢。很高兴听到这个消息。我确实花了很长时间才弄清楚这一切并让它发挥作用,所以我真的很高兴我帮助了这么多人面临同样的问题。这就是 SO 的精髓所在。
    猜你喜欢
    • 1970-01-01
    • 2012-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多