如何使用RtdServer在C#中创建实时Excel自动化加载项?

我的任务是使用RtdServer在C#中编写实时Excel自动化加载项。 我非常依赖Stack Overflow中遇到的知识。 我决定表达我的感谢,写下如何记录所有我学到的东西。 Kenny Kerr的Excel RTD服务器:最小的C#实现文章帮助我入门。 我发现Mike Rosenblum和Govert的评论特别有帮助。

(作为下述方法的替代方案,您应该考虑使用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 _topics = new Dictionary(); 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)右键单击项目属性并检查以下内容:应用程序>assembly信息…>使assemblyCOM-Visible和Build>注册COM Interop

3.1)右键单击项目Add Reference …> .NET选项卡> Microsoft.Office.Interop.Excel

4)构建解决方案(F6)

5)运行Excel。 转到Excel选项>加载项>管理Excel加载项>自动化,然后选择“StackOverflow.RtdServer”

6)在单元格中输入“= RTD(”StackOverflow.RtdServer.ProgId“,200)”。

7)交叉你的手指,希望它有效!

从计时器线程调用UpdateNotify最终会导致奇怪的错误或与Excel断开连接。

必须仅从调用ServerStart()的同一线程调用UpdateNotify()方法。 它没有在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 

}

按照前面两个RTD服务器的答案为我工作。 但是,在运行Excel x64的x64计算机上遇到问题。 就我而言,在我将项目的“目标平台”切换到x64之前,Excel总是显示#N / A.