Как получить доступ к модулю класса в Excel VBA из C #? - PullRequest
2 голосов
/ 13 ноября 2009

У меня есть надстройка Excel с модулем класса. Я хочу создать экземпляр модуля класса в C # и вызвать метод для него. Как мне это сделать?

Ответы [ 2 ]

3 голосов
/ 14 ноября 2009

Если вам действительно нужен доступ к экземпляру класса, вы можете сделать следующее:

  • Создание библиотеки типов для интерфейса COM, который вы хотите предоставить из своего класса VBA (например, IMyComInterface)

  • Добавьте ссылку на эту библиотеку типов в ваш проект VBA

  • Реализуйте интерфейс в своем модуле класса VBA - например, MyVbaClass (используйте ключевое слово Implements):

    Option Explicit
    Implements IMyComInterface
    
    Private Sub IMyComInterface_SomeMethod(...)
        ...
    End Sub
    ...
    
  • Ссылка на ту же библиотеку типов в вашем проекте C #

  • Создайте класс ComVisible C # с методом, который принимает ссылку на экземпляр интерфейса VBA. Что-то вроде:

    public class MyVbaLoader
    {
        public IMyComInterface MyComInterface
        {
            get { return myComInterface; }
            set { myComInterface = value; }
        }
    }
    
  • Написать «фабричный» метод в стандартном модуле VBA, который принимает объект в качестве параметра ByRef. Этот объект должен предполагать, что объект, переданный в качестве аргумента, имеет свойство «MyComInterface» и должен установить это свойство для нового экземпляра класса VBA MyClass.

    Public Sub MyFactoryMethod(MyVbaLoader As Object) 
        Dim objClass As MyVbaClass
        Set objClass = New MyVbaClass
        ... any initialization of objClass here ...
    
        ' Pass a reference to the VBA class to the C# object MyVbaLoader
        MyVbaLoader.MyComInterface = objClass
    End Sub
    
  • Вызовите метод фабрики из вашего кода C #. Предполагая, что вы открыли рабочую книгу и в своем коде VBA есть справочная «рабочая книга», код будет выглядеть примерно так:

    MyVbaLoader loader = new MyVbaLoader();
    workbook.Application.Run("MyModule.MyFactoryMethod", loader, Type.Missing, ... , Type.Missing);
    // we now have a reference to the VBA class module in loader.MyComInterface
    // ...
    

Как видите, это довольно сложно. Без более подробной информации о проблеме, которую вы пытаетесь решить, трудно сказать, оправдана ли эта сложность или есть более простое решение.

Если вышесказанное неясно, дайте мне знать, и я постараюсь уточнить.

Как правило, вы не можете вернуть значение из макроса VBA, вызванного из кода C # с помощью Application.Run, поэтому вам приходится прибегать к передаче объекта по значению, у которого есть метод или свойство, которое можно вызвать из VBA для установки экземпляр.

2 голосов
/ 14 ноября 2009

Модули класса VBA имеют только два режима создания экземпляров: закрытый и общедоступный. Таким образом, вы даже не можете создать их экземпляр в другом проекте VB (A), не говоря уже о C #.

Однако ничто не мешает вам иметь стандартный модуль, который действует как фабрика классов. Таким образом, если ваш модуль класса Foo, то в стандартном модуле вы можете иметь метод NewFoo, который создает для вас новый Foo и возвращает его вызывающей стороне. Объект Foo, очевидно, должен был бы быть открытым для общественности.

[Ваш метод NewFoo может принимать параметры, поэтому вы можете моделировать параметризованные конструкторы, которые недоступны в VBA.]

РЕДАКТИРОВАТЬ : подробности о том, как вызвать функцию VBA (в стандартном модуле) из C # и получить возвращаемое значение, используя Application.Run.

private static object RunMacro(Excel.Application excelApp, string macroName, object[] parameters)
{
    Type applicationType = excelApp.GetType();

    ArrayList arguments = new ArrayList();

    arguments.Add(macroName);

    if (parameters != null)
        arguments.AddRange(parameters);

    try
    {
        return applicationType.InvokeMember("Run", BindingFlags.Default | BindingFlags.InvokeMethod, null, excelApp, arguments.ToArray());
    }
    catch (TargetInvocationException ex)
    {
        COMException comException = ex.InnerException as COMException;

        if (comException != null)
        {
            // These errors are raised by Excel if the macro does not exist

            if (    (comException.ErrorCode == -2146827284)
                ||  (comException.ErrorCode == 1004))
                throw new ApplicationException(string.Format("The macro '{0}' does not exist.", macroName), ex);
        }

        throw ex;
    }
}

Обратите внимание, что вы можете опустить все, что попытаться ... поймать вещи - все, что он делает, это обрабатывает конкретную ошибку, где макрос не существует, и вызывает более значимое исключение в этом случае. object, возвращаемый функцией, может быть приведен к любому типу, который вам нужен. Например:

object o = RunMacro(excelApp, "MyModule.MyFunc", new object[] { "param1", 2 });
if (o is string)
{
    string s = (string) o;
    Console.WriteLine(s);
}

Предполагая, что функция на самом деле возвращает экземпляр вашего объекта класса, определенного VBA, вы можете вызывать методы этого объекта таким же образом, снова используя InvokeMember:

object o = RunMacro(excelApp, "MyModule.MyFunc", new object[] { "param1", 2 });

// assume o is an instance of your class, and that it has a method called Test that takes no arguments
o.GetType().InvokeMember("Run", BindingFlags.Default | BindingFlags.InvokeMethod, null, o, new string[] {"Test"});

Если вы делаете много таких вызовов, то, очевидно, вы можете скрыть ужасные детали, создав методы-обертки, чтобы сделать вызов для вас.

Обратите внимание, что я набрал большую часть из памяти, поэтому я полностью ожидаю, что там будут синтаксические, логические и, возможно, даже фактические ошибки: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...