Ответ 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, перейдя в "Инструменты > Ссылки":
Затем вы сможете получить доступ к своему основному классу и любым другим классам следующим образом:
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.