Получение экземпляра EnvDTE.DTE вне среды разработки Visual Studio

Я создаю инструмент автоматизации проекта в 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) 

Я хочу знать, есть ли способ получить активный экземпляр EnvDTE.DTE вне Visual Studio IDE .?

3 ответа

Решение

Автоматизация существующего экземпляра 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 занят, пункт меню или кнопка панели инструментов не могут быть нажаты (если операция "занят" не асинхронна).

Здесь я нашел альтернативу GetActiveObject, где Кирилл объясняет, как перечислять ROT. Есть и другие примеры на MSDN.

Поскольку некоторые пользователи SO не любят ссылки, вот подробности:

  • Перечислите все процессы с именем devenv.exe.
  • Показать список заголовков главного окна. (Я снимаю "Microsoft Visual Studio" с конца)
  • Спросите пользователя, какой он хочет использовать.
  • Используйте process.Id, чтобы найти объект в ROT, который, как я считаю, является вопросом ОП. Это делается путем перечисления ROT с помощью IEnumMoniker.Next(), который возвращает моникеры и идентификаторы процесса (в случае 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. Если вы получили ошибку, повторите попытку в течение нескольких секунд. Наконец, всплывающее окно сообщения, если это необходимо.

Некоторым пользователям SO не нравятся ссылки, потому что они не работают;)

На написание моей версии у меня ушло больше часа, так что можно разместить ее здесь. Требуются ссылки на envdte а также envte80(из добавления ссылок / сборок / расширений). Предоставляет статический метод для открытия файла C # в Visual Studio или Notepad++ в качестве резервной копии и, при необходимости, перехода к определенной строке.

      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);
    }
}
Другие вопросы по тегам