简体   繁体   English

在 Visual Studio 外获取 EnvDTE.DTE 实例 IDE

[英]Getting EnvDTE.DTE instance outside Visual Studio IDE

I am creating a project automation tool in Visual Studio 2013 where I have my own project template and I am trying to add it to an existing solution programatically.I am using the following code in a console application.我在 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);

It is working when I run the application from Visual Studio IDE. But when I try to run the exe from command prompt, I get the following exception.当我从 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) 

I want to know whether there is any way to get the active EnvDTE.DTE instance outside Visual Studio IDE.?我想知道是否有任何方法可以在 Visual Studio IDE 之外获取活动的 EnvDTE.DTE 实例。?

Automating an existing Visual Studio instance from an external tool to modify a loaded solution is a bad idea. 从外部工具自动化现有的Visual Studio实例来修改加载的解决方案是一个坏主意。 If you use GetActiveObject(...) and there are two Visual Studio instances launched, how do you know that the correct instance is returned? 如果您使用GetActiveObject(...)并且启动了两个Visual Studio实例,您如何知道返回正确的实例? And what if the user or Visual Studio is doing something with the solution when the user launches the external tool? 如果用户启动外部工具时用户或Visual Studio正在使用解决方案做些什么呢? There are two better approaches: 有两种更好的方法:

1) Use an external tool to automate a new Visual Studio instance , load the desired solution and modify it. 1)使用外部工具自动化新的Visual Studio实例 ,加载所需的解决方案并进行修改。 This can be done even with the VS instance not visible. 即使VS实例不可见,也可以这样做。 To create a new instance the proper code is: 要创建新实例,正确的代码是:

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

2) Use a Visual Studio extension, such as a macro (VS 2010 or lower), add-in (VS 2013 or lower) or package (any VS version) to provide a menu item or button toolbar that, when clicked, modifies the currently loaded solution. 2)使用Visual Studio扩展,例如宏(VS 2010或更低版本),加载项(VS 2013或更低版本)或包(任何VS版本),以提供菜单项或按钮工具栏,单击该工具栏时,修改目前加载解决方案 This prevent the "busy" scenario because if VS is busy the menu item or toolbar button can't be clicked (unless the "busy" operation is asynchronous). 这可以防止“忙”场景,因为如果VS忙,则无法单击菜单项或工具栏按钮(除非“忙”操作是异步的)。

I found an alternative to GetActiveObject, here , where Kiril explains how to enumerate the ROT. 我在这里找到了GetActiveObject的替代方案,其中Kiril解释了如何枚举ROT。 There are other examples on MSDN. MSDN上还有其他示例。

Since some SO users don't like links here are the details: 由于一些SO用户不喜欢这里的链接是详细信息:

  • Enumerate all of the processes, named devenv.exe. 枚举所有名为devenv.exe的进程。
  • Show a list of main window titles. 显示主窗口标题列表。 (I strip "Microsoft Visual Studio" off the end) (我删除了“Microsoft Visual Studio”)
  • Ask the user which one they want to use. 询问用户他们想要使用哪一个。
  • Use the process.Id to find an object in the ROT, which I believe is the OP's question. 使用process.Id在ROT中找到一个对象,我认为这是OP的问题。 This is done by enumerating the ROT using IEnumMoniker.Next(), which returns monikers and process id's (in the case of VS). 这是通过使用IEnumMoniker.Next()枚举ROT来完成的,它返回了monikers和进程id(在VS的情况下)。

  • Having found the moniker. 找到了绰号。 Cast the running object to a DTE and off you go. 将运行对象投射到DTE然后离开。

COM, ROT and Moniker sounded too complex for me, so I was happy to see that the heavy lifting had already been done at the link above. COM,ROT和Moniker对我来说听起来太复杂了,所以我很高兴地看到在上面的链接已经完成了繁重的工作。

I had the example working in a couple of minutes. 我让这个例子在几分钟内完成。 It worked the first time I stepped through with the debugger. 它是我第一次使用调试器时工作。 But at full speed, I needed to add some sleeps or retries, because it is easy to get an exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER)) 但是在全速运行时,我需要添加一些睡眠或重试,因为很容易从HRESULT中获取异常:0x8001010A(RPC_E_SERVERCALL_RETRYLATER))

Also, I replaced the exact match with a regex that tolerates other versions of VS: 另外,我用一个容忍其他版本VS的正则表达式替换了完全匹配:

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

The issue where VS may be busy, with an open dialog, or compiling many projects is common to many applications you might try to force feed key strokes or COM requests. VS可能繁忙,打开对话框或编译许多项目的问题对于许多应用程序来说很常见,您可能会尝试强制执行提要键击或COM请求。 If you get an error retry for a few seconds. 如果您在几秒钟内重试错误。 Finally, pop up a message box if needed. 最后,如果需要,弹出一个消息框。

Some SO users don't like links because links get broken;)一些 SO 用户不喜欢链接,因为链接会损坏;)

Took me over an hour to write my version so might as well post it here.我花了一个多小时来写我的版本,所以不妨把它贴在这里。 Requires references to envdte and envte80 (from add references / assemblies / extensions).需要对envdteenvte80的引用(来自添加引用/程序集/扩展)。 Provides static method for opening a C# file in Visual Studio or Notepad++ as a backup and optionally navigate to a specific line.提供 static 方法,用于在 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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM