WPF .NET 8 64位应用中实现Excel RTD服务器的可行性问询
关于WPF .NET 8 64位应用实现Excel RTD服务器的问题
Excel RTD是基于COM的技术,目前能找到的所有示例都具备以下特征:
- 基于.NET Framework(而非.NET Core或现代.NET)
- 32位
- 进程内COM服务器(DLL),调用Excel RTD函数时由COM系统实例化
我有一个WPF .NET 8 64位应用,需要在其中实现RTD服务器,但不确定是否可行。
我已参照示例实现了一个类(代码如下)。作为进程外COM服务器,且需要Excel连接到运行中的实例而非启动新的服务器实例,我还使用了ROT(运行对象表)注册。
目前最接近成功的状态是Excel能识别到注册信息,但它仍尝试启动新的服务器实例(从已注册的EXE启动新进程)。实例启动后,Excel会冻结约20-30秒,可能是在尝试连接,最终报错并在单元格中显示#N/A。
我的问题如下:
- 我尝试实现的功能是否可行?
- 若可行,有没有可参考的示例或资源?
- 若不可行,在WPF、64位、.NET 8的场景下,有什么替代方法能实现类似效果?
[Guid(ClsId)] [ProgId(ProgId)] [ComVisible(true)] public class RTDServer : IRtdServer { const string ClsId = "575A6DCF-5EDE-419C-9AFF-AFD0745D711C"; const string ProgId = "Catamount.Pounce.RtdServer"; ~RTDServer() { UnregisterFromROT(); } public static void Start() { Register(); RTDServer rtdServer = new RTDServer(); rtdServer.RegisterInROT(); AppDomain.CurrentDomain.ProcessExit += (s, e) => UnregisterFromROT(); AppDomain.CurrentDomain.DomainUnload += (s, e) => UnregisterFromROT(); } static IRunningObjectTable? _rot; static IMoniker? _moniker; static int _rotCookie; public void RegisterInROT() { GetRunningObjectTable(0, out IRunningObjectTable rot); _rot = rot; CreateItemMoniker("!", ProgId, out IMoniker moniker); _moniker = moniker; _rotCookie = _rot.Register(0, Marshal.GetIUnknownForObject(this), _moniker); } public static void UnregisterFromROT() { if (_rot != null && _rotCookie != 0) { _rot.Revoke(_rotCookie); _rot = null; _moniker = null; _rotCookie = 0; } } [DllImport("ole32.dll")] static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot); [DllImport("ole32.dll")] static extern int CreateItemMoniker(string lpszDelim, string lpszItem, out IMoniker ppmk); public static void Register() { try { string exePath = Process.GetCurrentProcess().MainModule?.FileName ?? throw new InvalidOperationException("Unable to determine the executable path."); using (RegistryKey? key = Registry.CurrentUser.OpenSubKey($@"Software\Classes\CLSID\{{{ClsId}}}")) if (key != null) return; using (RegistryKey clsidKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\CLSID\{{{ClsId}}}")) { clsidKey.SetValue(null, ProgId); using (RegistryKey localServer32Key = clsidKey.CreateSubKey("LocalServer32")) { localServer32Key.SetValue(null, exePath); } } using (RegistryKey progIdKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{ProgId}")) { progIdKey.SetValue(null, ProgId); using (RegistryKey clsidSubKey = progIdKey.CreateSubKey("CLSID")) { clsidSubKey.SetValue(null, $"{{{ClsId}}}"); } } } catch (Exception ex) { Debug.WriteLine($"Error registering COM server: {ex.Message}"); throw; } } IRTDUpdateEvent? _callback; Dictionary<int, string> _topics = new Dictionary<int, string>(); public int ServerStart(IRTDUpdateEvent callback) { _callback = callback; return 1; } public void ServerTerminate() { _topics.Clear(); } public object ConnectData(int topicId, ref Array strings, ref bool newValues) { _topics[topicId] = strings.GetValue(0)?.ToString() ?? string.Empty; return "Initial Data"; } public void DisconnectData(int topicId) { if (_topics.ContainsKey(topicId)) { _topics.Remove(topicId); } } public Array RefreshData(ref int topicCount) { object[,] data = new object[2, _topics.Count]; int index = 0; foreach (var topic in _topics) { data[0, index] = topic.Key; data[1, index] = "Updated Data"; index++; } topicCount = _topics.Count; return data; } public int Heartbeat() { return 1; } }
后续补充信息(解决后整理)
为感兴趣的用户整理以下资源:
- Kenny Kerr的系列文章,覆盖Excel RTD服务器在C#中的实现及多个进阶场景
- Simon Mourier的开源解决方案,不仅实现了可用的RTD服务器,还展示了如何从运行中的进程提供服务,而非传统的进程内DLL
内容的提问来源于stack exchange,提问作者Franco Tiveron
相关产品推荐
相关产品推荐

