【问题标题】:How do I create a real-time Excel automation add-in in C# using RtdServer?如何使用 RtdServer 在 C# 中创建实时 Excel 自动化加载项?
【发布时间】:2011-03-22 20:44:19
【问题描述】:

我的任务是使用 RtdServer 在 C# 中编写一个实时 Excel 自动化插件。我非常依赖在 Stack Overflow 中学到的知识。为了表达我的谢意,我决定写一个如何将我所学的知识联系在一起的文档。 Kenny Kerr 的Excel RTD Servers: Minimal C# Implementation 文章帮助我入门。我发现 Mike RosenblumGovert 的 cmets 特别有用。

【问题讨论】:

  • 辛苦了,我记得我第一次尝试让其中一个工作的人没有任何例子。我相信这会对某人有所帮助:) 我会做两件事。 1) 您可以创建一个 xla,它将对 RTD 的调用封装在一个函数中,该函数将为您提供更清晰、更清晰的语法和 Excel 端的错误处理。其次,如果您执行任何 VSTO 操作,以及使用 .net 编码的 IRTDServer 进行较小扩展的操作,请使用 Excel 中的选项对话框来模拟 Excel 被阻止。你的代码必须处理它。
  • 非常正确。来自 Excel (RefreshData) 的拉取请求可能会在您调用 UpdateNotify 后立即发出,但有很多事情可能会无限期地延迟它(对话框、输入公式等)。你不能永远排队更新。
  • 我也同意,一般来说你不想强迫人类直接调用 RTD 函数。在 VBA 中创建包装函数很容易。我不确定如何在 C# 中创建包装函数,并发布了与 here 相关的问题
  • @Frank - 我不确定如何在 c# 中编写包装器,但如果您要在 c++ 中编写 UDF 包装器 - 您可以使用 xlfRtd(excel 2007+ c api 的一部分)来将调用包装到您的 rtd 服务器。

标签: c# vsto excel-addins rtd excel-udf


【解决方案1】:

(作为下面描述的方法的替代方法,您应该考虑使用Excel-DNA。Excel-DNA 允许您构建免注册 RTD 服务器。COM 注册需要管理权限,这可能会导致安装问题。话虽如此,下面的代码工作正常。)

使用 RtdServer 在 C# 中创建实时 Excel 自动化插件:

1)在Visual Studio中创建一个C#类库项目,输入以下内容:

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) 交叉手指,希望它有效!

【讨论】:

  • Excel如何找到带有ProgId的文件所在的位置?我正在尝试将我的 Excel 插件移动到另一台机器上,但我无法在 Excel 上设置它
  • 这个例子中Trace写到什么路径?我找不到它。
  • Sysytem.Diagnostics.Trace 与 System.Console 类似,但更灵活。它允许您执行诸如添加跟踪级别和添加多个侦听器之类的操作。
  • RtdServer的GUID属性有什么意义?我需要指定它吗?我从哪里得到它?
  • COM 注册需要 GUID。您可以使用工具 > 在 Visual Studio 中创建 GUID 生成 GUID。
【解决方案2】:

从计时器线程调用 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

【讨论】:

  • 非常好(另一个选择是有一个线程安全的集合来读取/写入)。但是,从纯 C# 类库中,定位 DispatcherSynchronizationContext 需要哪些额外的引用?我找不到它(我怀疑这是 WPF 特有的东西)。
【解决方案3】:

按照 RTD 服务器的前两个答案对我有用。但是,我在运行 Excel x64 的 x64 机器上遇到了问题。就我而言,在我将项目的“目标平台”切换到 x64 之前,Excel 始终显示#N/A。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 1970-01-01
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多