【问题标题】:Getting EnvDTE.DTE instance outside Visual Studio IDE在 Visual Studio IDE 之外获取 EnvDTE.DTE 实例
【发布时间】:2014-12-08 11:04:34
【问题描述】:

我正在 Visual Studio 2013 中创建一个项目自动化工具,我在其中拥有自己的项目模板,并尝试以编程方式将其添加到现有解决方案中。我在控制台应用程序中使用以下代码。

EnvDTE.DTE dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE.12.0");
string solDir = dte.Solution.FullName;
solDir=solDir.Substring(0, solDir.LastIndexOf("\\"));
dte.Solution.AddFromTemplate(path, solDir+"\\TestProj", "TestProj", false);

当我从 Visual Studio IDE 运行应用程序时,它正在工作。但是当我尝试从命令提示符运行 exe 时,出现以下异常。

Unhandled Exception: System.Runtime.InteropServices.COMException: Operation unav
ailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE))
at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object&   ppunk)
at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID)
at ProjectAutomation.Console.Program.Main(String[] args) 

我想知道是否有任何方法可以在 Visual Studio IDE 之外获取活动的 EnvDTE.DTE 实例。?

【问题讨论】:

  • Marshal.GetActiveObject("VisualStudio.DTE.12.0") 为我工作。将 exception.ToString() 结果添加到问题中。
  • 我在一个普通的控制台应用程序中使用了上面的代码。我应该使用 Visual Studio 中的任何可扩展项目模板吗?
  • 我的水晶球说 VS 正在运行,但你的控制台应用程序没有。
  • 仅供参考,如果有多个实例在运行,您的代码将无法按预期运行。

标签: c# visual-studio-2013 envdte


【解决方案1】:

从外部工具自动化现有的 Visual Studio 实例以修改加载的解决方案是个坏主意。如果您使用 GetActiveObject(...) 并且启动了两个 Visual Studio 实例,您如何知道返回了正确的实例?当用户启动外部工具时,如果用户或 Visual Studio 正在对解决方案执行某些操作怎么办?有两种更好的方法:

1) 使用外部工具自动化新的 Visual Studio 实例,加载所需的解决方案并对其进行修改。即使 VS 实例不可见,也可以这样做。要创建一个新实例,正确的代码是:

System.Type type = Type.GetTypeFromProgID("VisualStudio.DTE.12.0");
EnvDTE.DTE dte = (EnvDTE.DTE) System.Activator.CreateInstance(type);
dte.MainWindow.Visible = true;
...

2) 使用 Visual Studio 扩展,例如宏(VS 2010 或更低版本)、加载项(VS 2013 或更低版本)或包(任何 VS 版本)来提供菜单项或按钮工具栏,当单击时, 修改当前加载的解决方案。这可以防止“忙碌”的情况,因为如果 VS 忙碌,则无法单击菜单项或工具栏按钮(除非“忙碌”操作是异步的)。

【讨论】:

  • 如果我们通过 dte.Solution.Open() 加载解决方案并将另一个项目添加到解决方案中是否正确..?
  • 是的,您可以创建一个新的 DTE 实例,卸载现有的解决方案(如果有)(以防 VS 设置为加载最后使用的解决方案),加载所需的解决方案并添加一个项目。
  • 感谢MainWindow.Visible!!!这是我见过的唯一使用它的帖子。我绝望地尝试通过SuppressUI 做同样的事情。 (我知道你的网站/博客,我真的认为你是 VS 可扩展性的全球专家。)
【解决方案2】:

我找到了 GetActiveObject 的替代方法,here,Kiril 在这里解释了如何枚举 ROT。 MSDN 上还有其他示例。

由于一些 SO 用户不喜欢这里的链接,所以详细信息:

  • 枚举所有名为 devenv.exe 的进程。
  • 显示主窗口标题列表。 (我把“Microsoft Visual Studio”去掉)
  • 询问用户他们想使用哪一个。
  • 使用 process.Id 在 ROT 中查找对象,我认为这是 OP 的问题。这是通过使用 IEnumMoniker.Next() 枚举 ROT 来完成的,IEnumMoniker.Next() 返回名字对象和进程 ID(在 VS 的情况下)。

  • 已找到绰号。将正在运行的对象投射到 DTE 上,然后出发。

COM、ROT 和 Moniker 对我来说听起来太复杂了,所以我很高兴看到上面的链接已经完成了繁重的工作。

我在几分钟内完成了这个示例。当我第一次使用调试器时,它就起作用了。但全速运行时,我需要添加一些休眠或重试,因为很容易从 HRESULT 中获取异常:0x8001010A (RPC_E_SERVERCALL_RETRYLATER))

另外,我将完全匹配替换为允许其他版本 VS 的正则表达式:

Regex monikerRegex = new Regex(@"!VisualStudio.DTE\.\d+\.\d+\:" + processId, RegexOptions.IgnoreCase);

VS 可能很忙、打开对话框或编译许多项目的问题对于您可能尝试强制输入击键或 COM 请求的许多应用程序很常见。如果出现错误,请重试几秒钟。最后,如果需要,弹出一个消息框。

【讨论】:

    【解决方案3】:

    一些 SO 用户不喜欢链接,因为链接坏了;)

    我花了一个多小时来写我的版本,所以不妨把它贴在这里。需要对 envdteenvte80 的引用(来自添加引用/程序集/扩展)。提供在 Visual Studio 或 Notepad++ 中打开 C# 文件作为备份的静态方法,并可选择导航到特定行。

    using System;
    using System.Diagnostics;
    using EnvDTE80;
    using System.Runtime.InteropServices.ComTypes;
    using System.Runtime.InteropServices;
    
    namespace whatever
    {
        public static class CsFile
        {
            public static void Open(string fileName, int? lineNr = null)
            {
                try
                {
                    OpenFileInVisualStudio(fileName, lineNr);
                }
                catch
                {
                    try
                    {
                        OpenFileInNotePadPlusPlus(fileName, lineNr);
                    }
                    catch
                    {
                        // Woe is me for all has failed. Somehow show an error.
                    }
                }
            }
    
            public static void OpenFileInVisualStudio(string fileName, int? lineNr = null)
            {
                DTE2 dte = null;
                TryFor(1000, () => dte = GetDteByName("VisualStudio.DTE"));
                if (dte == null) throw new Exception("Visual Studio not running?");
                dte.MainWindow.Activate();
                TryFor(1000, () => dte.ItemOperations.OpenFile(fileName));
                if (lineNr.HasValue) TryFor(1000, () => ((EnvDTE.TextSelection)dte.ActiveDocument.Selection).GotoLine(lineNr.Value, true));
            }
    
            public static void OpenFileInNotePadPlusPlus(string fileName, int? lineNr = null)
            {
                if (lineNr.HasValue) fileName += " -n" + lineNr.Value.ToString();
                Process.Start(@"C:\Program Files (x86)\Notepad++\notepad++.exe", fileName);
            }
    
            private static void TryFor(int ms, Action action)
            {
                DateTime timeout = DateTime.Now.AddMilliseconds(ms);
                bool success = false;
    
                do
                {
                    try
                    {
                        action();
                        success = true;
                    }
                    catch (Exception ex)
                    {
                        if (DateTime.Now > timeout) throw ex;
                    }
                } while (!success);
            }
    
            static DTE2 GetDteByName(string name)
            {
                IntPtr numFetched = Marshal.AllocHGlobal(sizeof(int));
    
                IRunningObjectTable runningObjectTable;
                IEnumMoniker monikerEnumerator;
                IMoniker[] monikers = new IMoniker[1];
    
                IBindCtx bindCtx;
                Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
    
                bindCtx.GetRunningObjectTable(out runningObjectTable);
    
                runningObjectTable.EnumRunning(out monikerEnumerator);
                monikerEnumerator.Reset();
    
                while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
                {
                    IBindCtx ctx;
                    CreateBindCtx(0, out ctx);
    
                    string runningObjectName;
                    monikers[0].GetDisplayName(ctx, null, out runningObjectName);
    
                    if (runningObjectName.Contains(name))
                    {
                        object runningObjectVal;
                        runningObjectTable.GetObject(monikers[0], out runningObjectVal);
    
                        DTE2 dte = (DTE2)runningObjectVal;
                        return (dte);
                    }
                }
                return null;
            }
    
            [DllImport("ole32.dll")]
            private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多