¿Cómo creo un complemento de automatización de Excel en tiempo real en C# usando RTDServer?

StackOverflow https://stackoverflow.com/questions/5397607

Pregunta

Me encargaron escribir un complemento de automatización de Excel en tiempo real en C# usando RTDSERVER para el trabajo. Confié en gran medida sobre el conocimiento que encontré en el desbordamiento de pila. He decidido expresar mi agradecimiento escribiendo un cómo documentar que une todo lo que he aprendido. Kenny Kerr's Servidores de Excel RTD: implementación mínima de C# El artículo me ayudó a comenzar. Encontré comentarios por Mike Rosenblum y Gobierno Especialmente útil.

¿Fue útil?

Solución

(Como alternativa al enfoque descrito a continuación, debe considerar usar DNA de Excel. Excel-DNA le permite construir un servidor RTD sin registro. El registro de COM requiere privilegios administrativos que pueden conducir a dolores de cabeza de instalación. Dicho esto, el siguiente código funciona bien).

Para crear un complemento de automatización de Excel en tiempo real en C# usando rtdserver:

1) Cree un proyecto de biblioteca de clase C# en Visual Studio e ingrese lo siguiente:

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) Haga clic derecho en el proyecto y agregue> Nuevo elemento ...> clase de instalador. Cambie a la vista del código e ingrese lo siguiente:

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) Haga clic derecho en las propiedades del proyecto y verifique lo siguiente: Aplicación> Información de ensamblaje ...> Hacer el ensamblaje Com-visible y construir> Regístrese para COM Intop

3.1) Haga clic derecho en el proyecto Agregar referencia ...> pestaña .NET> Microsoft.Office.inTerop.excel

4) Solución de construcción (F6)

5) Ejecute Excel. Vaya a Opciones de Excel> complementos> Administrar Add-Ins de Excel> Automatización y seleccione "StackOverflow.Rtdserver"

6) Ingrese "= rtd (" stackoverflow.rtdserver.progid ",, 200)" en una celda.

7) ¡Cruza los dedos y espero que funcione!

Otros consejos

Llamar a UpdateNotify desde el hilo del temporizador eventualmente causará errores extraños o desconexiones de Excel.

El método UpdateTify () solo debe llamarse desde el mismo hilo que llama a serverstart (). No está documentado en la ayuda de RTDServer, pero es la restricción de com.

La solución es simple. Use despacharsynCronizationContext para capturar el hilo que llama a ServerStart y usarlo para enviar llamadas a UpdateTify:

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

Después de las dos respuestas anteriores para el servidor RTD funcionó para mí. Sin embargo, me encontré con un problema cuando estaba en una máquina X64 que ejecuta Excel X64. En mi caso, hasta que cambié la "plataforma objetivo" del proyecto a X64, Excel siempre mostró #N/A.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top