简体   繁体   中英

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.

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.

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.?

Automating an existing Visual Studio instance from an external tool to modify a loaded solution is a bad idea. If you use GetActiveObject(...) and there are two Visual Studio instances launched, how do you know that the correct instance is returned? And what if the user or Visual Studio is doing something with the solution when the user launches the external tool? There are two better approaches:

1) Use an external tool to automate a new Visual Studio instance , load the desired solution and modify it. This can be done even with the VS instance not visible. 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. 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).

I found an alternative to GetActiveObject, here , where Kiril explains how to enumerate the ROT. There are other examples on MSDN.

Since some SO users don't like links here are the details:

  • Enumerate all of the processes, named devenv.exe.
  • Show a list of main window titles. (I strip "Microsoft Visual Studio" off the end)
  • 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. This is done by enumerating the ROT using IEnumMoniker.Next(), which returns monikers and process id's (in the case of VS).

  • Having found the moniker. Cast the running object to a DTE and off you go.

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.

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))

Also, I replaced the exact match with a regex that tolerates other versions of 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. 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;)

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). Provides static method for opening a C# file in Visual Studio or Notepad++ as a backup and optionally navigate to a specific line.

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);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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