Как создать надстройку автоматизации Excel в реальном времени на C # с помощью RtdServer?

Мне было поручено написать надстройку автоматизации Excel в реальном времени на C # с использованием RtdServer для работы. Я во многом полагался на знания, полученные в Stack Overflow. Я решил выразить свою благодарность, написав документ, который связывает воедино все, что я узнал. Помогла статья Кенни Керра Серверы RTD Excel: минимальная реализация C # мне начать. Я нашел комментарии Майка Розенблюма и Govert особенно полезно.


person Frank    schedule 22.03.2011    source источник
comment
Хорошие усилия, я помню, как в первый раз я попытался заставить кого-то из них работать, просто не было никаких примеров. Я уверен, что это кому-то поможет :) Я две вещи. 1) вы можете создать xla, который завершит вызов RTD в функции, которая предоставит вам более чистый и понятный синтаксис и обработку ошибок на стороне Excel. Во-вторых, если вы выполняете какие-либо работы с VSTO и, в меньшей степени, с IRTDServers, закодированными в .net, используйте диалоговое окно параметров в Excel, чтобы имитировать блокировку Excel. Ваш код должен с этим справиться.   -  person Ian    schedule 22.03.2011
comment
Совершенно верно. Запрос на вытягивание из Excel (RefreshData) может поступать почти сразу после вызова UpdateNotify, но есть много вещей, которые могут задерживать его на неопределенный срок (диалоговые окна, ввод формулы и т. Д.). Вы не можете ставить обновления в очередь вечно.   -  person Frank    schedule 23.03.2011
comment
Я также согласен с тем, что в целом вы не хотите заставлять человека напрямую вызывать функцию RTD. В VBA легко создать функцию-оболочку. Я не уверен, как создать функцию-оболочку в C #, и опубликовал связанный с этим вопрос здесь   -  person Frank    schedule 23.03.2011
comment
@Frank - я не уверен, как написать оболочку на C #, но если вы должны были написать оболочку UDF на c ++ - вы можете использовать xlfRtd (часть API-интерфейса excel 2007+ c), чтобы обернуть вызов на ваш rtd-сервер.   -  person quixver    schedule 05.02.2013


Ответы (3)


(В качестве альтернативы подходу, описанному ниже, вам следует рассмотреть возможность использования Excel-DNA. Excel-DNA позволяет создавать сервер RTD без регистрации. Для регистрации COM требуются права администратора, что может вызвать проблемы с установкой. При этом приведенный ниже код работает нормально.)

Чтобы создать надстройку автоматизации Excel в реальном времени на C # с помощью RtdServer:

1) Создайте проект библиотеки классов C # в Visual Studio и введите следующее:

using System;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace StackOverflow
{
    public class Countdown
    {
        public int CurrentValue { get; set; }
    }

    [Guid("EBD9B4A9-3E17-45F0-A1C9-E134043923D3")]
    [ProgId("StackOverflow.RtdServer.ProgId")]
    public class RtdServer : IRtdServer
    {
        private readonly Dictionary<int, Countdown> _topics = new Dictionary<int, Countdown>();
        private Timer _timer;

        public int ServerStart(IRTDUpdateEvent rtdUpdateEvent)
        {
            _timer = new Timer(delegate { rtdUpdateEvent.UpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
            return 1;
        }

        public object ConnectData(int topicId, ref Array strings, ref bool getNewValues)
        {
            var start = Convert.ToInt32(strings.GetValue(0).ToString());
            getNewValues = true;

            _topics[topicId] = new Countdown { CurrentValue = start };

            return start;
        }

        public Array RefreshData(ref int topicCount)
        {
            var data = new object[2, _topics.Count];
            var index = 0;

            foreach (var entry in _topics)
            {
                --entry.Value.CurrentValue;
                data[0, index] = entry.Key;
                data[1, index] = entry.Value.CurrentValue;
                ++index;
            }

            topicCount = _topics.Count;

            return data;
        }

        public void DisconnectData(int topicId)
        {
            _topics.Remove(topicId);
        }

        public int Heartbeat() { return 1; }

        public void ServerTerminate() { _timer.Dispose(); }

        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type t)
        {
            Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\Programmable");
            var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\InprocServer32", true);
            if (key != null)
                key.SetValue("", System.Environment.SystemDirectory + @"\mscoree.dll", Microsoft.Win32.RegistryValueKind.String);
        }

        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type t)
        {
            Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\Programmable");
        }
    }
}

2) Щелкните проект правой кнопкой мыши и выберите Добавить> Новый элемент ...> Класс установщика. Переключитесь в режим просмотра кода и введите следующее:

using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace StackOverflow
{
    [RunInstaller(true)]
    public partial class RtdServerInstaller : System.Configuration.Install.Installer
    {
        public RtdServerInstaller()
        {
            InitializeComponent();
        }

        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);

            var registrationServices = new RegistrationServices();
            if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
                Trace.TraceInformation("Types registered successfully");
            else
                Trace.TraceError("Unable to register types");
        }

        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);

            var registrationServices = new RegistrationServices();
            if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
                Trace.TraceInformation("Types registered successfully");
            else
                Trace.TraceError("Unable to register types");
        }

        public override void Uninstall(IDictionary savedState)
        {
            var registrationServices = new RegistrationServices();
            if (registrationServices.UnregisterAssembly(GetType().Assembly))
                Trace.TraceInformation("Types unregistered successfully");
            else
                Trace.TraceError("Unable to unregister types");

            base.Uninstall(savedState);
        }
    }
}

3) Щелкните правой кнопкой мыши на свойствах проекта и отметьте следующее: Приложение> Информация о сборке ...> Сделать сборку видимой для COM и Сборка> Зарегистрироваться для COM-взаимодействия.

3.1) Щелкните правой кнопкой мыши проект Добавить ссылку ...> вкладка .NET> Microsoft.Office.Interop.Excel

4) Построить решение (F6)

5) Запустите Excel. Перейдите в «Параметры Excel»> «Надстройки»> «Управление надстройками Excel»> «Автоматизация» и выберите «StackOverflow.RtdServer».

6) Введите "= RTD (" StackOverflow.RtdServer.ProgId ",, 200)" в ячейку.

7) Скрестите пальцы и надейтесь, что это сработает!

person Frank    schedule 18.04.2011
comment
Как Excel находит, где находится файл с ProgId? Я пытаюсь переместить надстройку Excel на другой компьютер, но не могу настроить его в Excel - person Alexey; 22.07.2013
comment
По какому пути Trace пишет в этом примере? Я не могу его найти. - person Grant Birchmeier; 25.10.2013
comment
Sysytem.Diagnostics.Trace похож на System. Консоль но более гибкая. Это позволяет вам делать такие вещи, как добавление уровней трассировки и добавление нескольких слушателей. - person Frank; 28.10.2013
comment
Какое значение имеет атрибут GUID RtdServer? Нужно ли мне это указывать? Откуда мне это взять? - person Grant Birchmeier; 29.01.2014
comment
GUID требуется для регистрации COM. Вы можете создать GUID, используя Инструменты ›Создать GUID в Visual Studio. - person Frank; 29.01.2014
comment
Кажется, он работает, даже если я не укажу GUID. (И спасибо за подсказку Excel-DNA; это здорово! Но все еще нужен этот RTD материал для обновления ячеек.) - person Grant Birchmeier; 30.01.2014
comment
Excel-DNA поддерживает RTD ... ознакомьтесь с образцами, которые поставляются с загрузкой. - person Frank; 30.01.2014
comment
@Frank, это действительно интересно. Есть ли эквивалент TopShelf для Excel RTD? TopShelf за кадром использует сборку System.Configuration.Install для начальной загрузки службы Windows. Вы можете увидеть исходный код здесь: github.com/Topshelf/Topshelf/ - person John Zabroski; 25.06.2019

Вызов UpdateNotify из потока таймера в конечном итоге приведет к странным ошибкам или отключению от Excel.

Метод UpdateNotify () должен вызываться только из того же потока, который вызывает ServerStart (). Это не описано в справке RTDServer, но это ограничение COM.

Исправить это просто. Используйте DispatcherSynchronizationContext, чтобы захватить поток, вызывающий ServerStart, и использовать его для отправки вызовов UpdateNotify:

public class RtdServer : IRtdServer
{
    private IRTDUpdateEvent _rtdUpdateEvent;
    private SynchronizationContext synchronizationContext;

    public int ServerStart( IRTDUpdateEvent rtdUpdateEvent )
    {
        this._rtdUpdateEvent = rtdUpdateEvent;
        synchronizationContext = new DispatcherSynchronizationContext();
        _timer = new Timer(delegate { PostUpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return 1;
    }


    // Notify Excel of updated results
    private void PostUpdateNotify()
    {
        // Must only call rtdUpdateEvent.UpdateNotify() from the thread that calls ServerStart.
        // Use synchronizationContext which captures the thread dispatcher.
        synchronizationContext.Post( delegate(object state) { _rtdUpdateEvent.UpdateNotify(); }, null);
    }

    // etc
} // end of class
person dlaudams    schedule 26.06.2014
comment
Очень хороший момент (другой вариант - иметь поточно-ориентированную коллекцию для чтения / записи). Однако какая дополнительная ссылка необходима для поиска DispatcherSynchronizationContext из чистой библиотеки классов C #? Я не могу его найти (и подозреваю, что это специфическая вещь для WPF). - person Carl Cook; 30.07.2017

Следующие два предыдущих ответа для сервера RTD сработали для меня. Однако я столкнулся с проблемой, когда на машине x64 работал с Excel x64. В моем случае, пока я не переключил «целевую платформу» проекта на x64, Excel всегда показывал # N / A.

person AnonymousUser    schedule 02.04.2016