You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

我的问题如下:

  1. 我尝试实现的功能是否可行?
  2. 若可行,有没有可参考的示例或资源?
  3. 若不可行,在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 00:42:03