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
相关产品推荐
相关产品推荐

