Comment créer un complément d'automatisation Excel en temps réel en C # à l'aide de RtdServer?

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

Question

J'ai été chargé d'écrire un complément d'automatisation Excel en temps réel en C # en utilisant RtdServer pour le travail.Je me suis fortement appuyé sur les connaissances que j'ai rencontrées dans Stack Overflow.J'ai décidé d'exprimer mes remerciements en rédigeant un document qui relie tout ce que j'ai appris.Article Excel RTD Servers: Minimal C # Implementation de Kenny Kerrm'a aidé à démarrer.J'ai trouvé des commentaires de Mike Rosenblum et de Govert particulièrement utile.

Était-ce utile?

La solution

(Comme alternative à l'approche décrite ci-dessous, vous devriez envisager d'utiliser Excel-DNA . Excel-DNA permet vous devez créer un serveur RTD sans inscription. L’enregistrement COM nécessite des privilèges administratifs, ce qui peut entraîner des problèmes d’installation. Cela étant dit, le code ci-dessous fonctionne correctement.)

Pour créer un complément d'automatisation Excel en temps réel en C # à l'aide de RtdServer:

1) Créez un projet de bibliothèque de classes C # dans Visual Studio et entrez ce qui suit:

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) Faites un clic droit sur le projet et Ajouter> Nouvel élément ...> Classe d'installation. Passez à la vue code et saisissez ce qui suit:

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) Cliquez avec le bouton droit sur les propriétés du projet et cochez ce qui suit: Application> Informations d'assemblage ...> Rendre l'assemblage COM-visible et construire> S'inscrire pour COM Interop

3.1) Faites un clic droit sur le projet Ajouter une référence ...> Onglet .NET> Microsoft.Office.Interop.Excel

4) Construire la solution (F6)

5) Exécutez Excel. Allez dans Options Excel> Compléments> Gérer les compléments Excel> Automatisation et sélectionnez "StackOverflow.RtdServer"

6) Entrez "= RTD (" StackOverflow.RtdServer.ProgId ",, 200)" dans une cellule.

7) Croisez les doigts et espérons que cela fonctionne!

Autres conseils

L'appel de UpdateNotify depuis le thread du minuteur entraînera éventuellement des erreurs étranges ou des déconnexions d'Excel.

La méthode UpdateNotify () ne doit être appelée qu'à partir du même thread qui appelle ServerStart ().Ce n'est pas documenté dans l'aide de RTDServer, mais c'est une restriction de COM.

Le correctif est simple.Utilisez DispatcherSynchronizationContext pour capturer le thread qui appelle ServerStart et utilisez-le pour distribuer les appels à 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

Suivre les deux réponses précédentes pour le serveur RTD a fonctionné pour moi.Cependant, j'ai rencontré un problème sur une machine x64 exécutant Excel x64.Dans mon cas, jusqu'à ce que je passe la "plate-forme cible" du projet à x64, Excel a toujours montré # N / A.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top