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

VS2017调用EnvDTE遇RPC_E_SERVERCALL_RETRYLATER错误求助

解决Visual Studio 2017中EnvDTE调用RPC_E_SERVERCALL_RETRYLATER异常问题

我正在移植一个小型控制台应用,用来删除磁盘上未包含在Visual Studio项目中的文件。这段代码在VS2013里运行正常,但在VS2017中抛出了以下COM异常:

System.Runtime.InteropServices.COMException: 'The message filter indicated that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))'

当前代码

public static int DeleteFilesNotInProject(string solutionFileAndPath, string projectName, string extension) { 
    var returnValue = 0; 
    EnvDTE80.DTE2 dte; 
    // Targets Visual Studio 2017 
    dte = (EnvDTE80.DTE2)Activator.CreateInstance(Type.GetTypeFromProgID("VisualStudio.DTE.15.0", true), true); 
    MessageFilter.Register(); 
    System.Threading.Thread.Sleep(2000); 
    while (!dte.Solution.IsOpen) { 
        // make sure it is ready to open 
        System.Threading.Thread.Sleep(500); 
        dte.Solution.Open(solutionFileAndPath); 
    } 
    dte.Solution.Open(solutionFileAndPath); 
    System.Threading.Thread.Sleep(5000); 
    foreach (Project project in dte.Solution.Projects) { 
        if (project.UniqueName.EndsWith(projectName)) 
            foreach (string s in GetFilesNotInProject(project, extension)) { 
                FileInfo fi = new FileInfo(s); 
                File.SetAttributes(s, FileAttributes.Normal); 
                File.Delete(s); 
                returnValue++; 
            } 
    } 
    dte.Quit(); 
    MessageFilter.Revoke(); 
    return returnValue; 
}

异常最初在while (!dte.Solution.IsOpen)行抛出,注释掉该行后,异常转而在foreach (Project project in dte.Solution.Projects)行抛出。

背景说明

  • 代码基于MSDN文章,希望保留为控制台应用程序
  • 已知PowerShell、VS插件/扩展方案,但希望保持为可在控制台应用中调用的C#类
  • 尝试仅使用EnvDTE而非EnvDTE80,仍出现相同错误

问题原因与解决方案

这个错误本质是Visual Studio的COM服务器在后台初始化或加载解决方案时处于忙碌状态,无法及时响应控制台应用的调用。虽然你注册了MessageFilter,但可能实现不够完善,同时代码里的硬编码Sleep和重复打开操作也会加剧这个问题。

1. 完善MessageFilter的实现

首先确保你的MessageFilter正确处理了COM服务器忙的重试逻辑,完整实现如下:

using System;
using System.Runtime.InteropServices;
using System.Threading;

public class MessageFilter : IOleMessageFilter
{
    // 注册消息过滤器到当前线程
    public static void Register()
    {
        IOleMessageFilter newFilter = new MessageFilter();
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(newFilter, out oldFilter);
    }

    // 撤销过滤器注册
    public static void Revoke()
    {
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(null, out oldFilter);
    }

    // 处理 incoming COM 调用
    int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)
    {
        return 0; // 允许正常调用
    }

    // 当调用被拒绝时决定是否重试
    int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
    {
        // 如果服务器忙,等待100ms后重试,最多重试5次(可按需调整)
        if (dwRejectType == 2) // RPC_E_SERVERCALL_RETRYLATER
        {
            Thread.Sleep(100);
            return 1; // 告诉COM服务器我们要重试
        }
        return -1; // 放弃调用
    }

    // 处理挂起的消息
    int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
    {
        return 2; // 继续等待,直到消息处理完成
    }

    // 导入COM注册函数
    [DllImport("ole32.dll")]
    private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
}

[ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleMessageFilter
{
    [PreserveSig]
    int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);

    [PreserveSig]
    int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);

    [PreserveSig]
    int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
}

2. 优化DTE初始化与解决方案加载逻辑

原来的代码有重复打开解决方案、硬编码Sleep等问题,替换为更可靠的等待逻辑,同时添加COM对象释放避免内存泄漏:

public static int DeleteFilesNotInProject(string solutionFileAndPath, string projectName, string extension)
{
    var returnValue = 0;
    EnvDTE80.DTE2 dte = null;

    try
    {
        // 注册消息过滤器,确保所有COM调用都经过它处理
        MessageFilter.Register();

        // 创建VS 2017 DTE实例
        dte = (EnvDTE80.DTE2)Activator.CreateInstance(Type.GetTypeFromProgID("VisualStudio.DTE.15.0", true), true);
        
        // 配置DTE后台运行,避免弹出VS界面
        dte.SuppressUI = true;
        dte.UserControl = false;

        // 打开解决方案
        dte.Solution.Open(solutionFileAndPath);

        // 等待解决方案完全加载完成,替代硬编码Sleep
        while (!dte.Solution.IsOpen || dte.Solution.SolutionBuild.State == EnvDTE.vsBuildState.vsBuildStateInProgress)
        {
            Thread.Yield(); // 让出线程资源
            Thread.Sleep(100);
        }

        // 遍历解决方案中的项目
        foreach (Project project in dte.Solution.Projects)
        {
            if (project.UniqueName.EndsWith(projectName))
            {
                foreach (string filePath in GetFilesNotInProject(project, extension))
                {
                    // 移除只读属性后删除文件
                    File.SetAttributes(filePath, FileAttributes.Normal);
                    File.Delete(filePath);
                    returnValue++;
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"操作出错: {ex.Message}");
        return -1;
    }
    finally
    {
        // 确保释放DTE对象并撤销过滤器
        if (dte != null)
        {
            dte.Quit();
            Marshal.ReleaseComObject(dte); // 释放COM对象,防止VS进程残留
        }
        MessageFilter.Revoke();
    }

    return returnValue;
}

3. 关键注意事项

  • 必须在所有COM调用之前注册MessageFilter,调用完成后撤销
  • 使用Marshal.ReleaseComObject(dte)释放COM对象,避免VS进程在后台残留
  • 设置SuppressUI = true和UserControl = false可以让VS在后台静默运行,避免弹出界面干扰
  • 替换硬编码Sleep为基于解决方案加载状态的等待,更贴合实际加载进度,减少不必要的等待时间

内容的提问来源于stack exchange,提问作者Josh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:34:00