Вызов метода библиотеки .net из vba

Я разработал веб-сервис в ASP.net, С# и размещен на IIS, который должен быть использован клиентом vba. Загрузив набор инструментов Office 2003 Web Services 2.01, я столкнулся с проблемой успешного создания необходимых прокси-классов (как это было зафиксировано многими пользователями в Интернете), и вместо этого решил создать библиотеку DLL.net. Я создал библиотеку, которая ссылается на веб-службу и предоставляет один из своих методов публичной функции в С#.

Теперь у меня есть три вопроса:

  • Как ссылаться на класс dll в VBA? Я попытался перейти в "Инструменты" > "Ссылки" и перейти в папку dll, но я получаю сообщение об ошибке "Не могу добавить ссылку на указанный файл". Есть ли определенное место на диске, которое я должен скопировать .dll?

  • Можно ли скопировать файл dll.config рядом с файлом dll, чтобы иметь там URL-адрес конечной точки?

  • Поскольку метод вызова принимает объект (состоящий из различных членов и пары участников List < > , как они могут быть реализованы в коде VBA?

Ответы

Ответ 1

Вам понадобится сделать COM-вызываемую оболочку (CCW) для вашей сборки (DLL). Совместимость с .NET - довольно глубокая тема, но относительно легко получить что-то с земли.

Прежде всего, вам нужно убедиться, что вся ваша сборка зарегистрирована для COM-взаимодействия. Вы можете сделать это на вкладке "Создать" в Visual Studio, установив флажок "Регистрация для COM-взаимодействия". Во-вторых, вы должны включить System.Runtime.InteropServices во все ваши классы:

using System.Runtime.InteropServices;

Затем вы должны украсить все классы, которые вы хотите открыть, с атрибутами [Serializable(), ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]. Это сделает так, чтобы вы могли нормально обращаться к членам класса и использовать intellisense из редактора 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.