Getting EnvDTE.DTE instance outside Visual Studio IDE
Asked Answered
W

3

8

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

Weigel answered 8/12, 2014 at 11:4 Comment(4)
Marshal.GetActiveObject("VisualStudio.DTE.12.0") works for me. Add exception.ToString() results to the question.Magenta
I have created used the above code in a normal console application. Should I use any of the extensible project templates in Visual Studio.?Weigel
My crystal ball says that VS is running elevated but your console app is not.Insinuation
FYI, your code would not work as expected if there was more than one instance running.Jolo
U
5

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

Unlimber answered 10/12, 2014 at 21:23 Comment(3)
Is it correct if we load the solution by dte.Solution.Open() and add another project to the solution..?Weigel
Yes, you can create a new instance of DTE, unload the existing solution if any (just in case VS is set to load the last used solution), load the desired solution and add a project.Unlimber
Thank you for MainWindow.Visible!!! This is the only post that I have seen which uses it. I was trying hopelessly to do the same through SuppressUI. (I know your site/blog and I really think you are the worldwide expert for VS Extensibility.)Ecliptic
D
4

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.

Duque answered 14/1, 2016 at 6:24 Comment(0)
L
4

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);
    }
}
Licastro answered 30/9, 2021 at 16:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.