如何在当前的Visual Studio解决方案中获取项目列表?

23

当我们在任何打开的解决方案中打开包管理器控制台时,它会显示该解决方案的所有项目。它是如何加载同一解决方案的所有项目的呢? 我尝试了下面显示的代码,它正在提取我已经打开的第一个解决方案的项目。

    private List<Project> GetProjects()
    {
        var dte = (DTE)Marshal.GetActiveObject(string.Format(CultureInfo.InvariantCulture, "VisualStudio.DTE.{0}.0", targetVsVersion));
        var projects = dte.Solution.OfType<Project>().ToList();
        return projects;
    }

包管理器控制台是一个进程内的VS扩展,它从来没有任何问题找到正确的VS实例。插件在OnConnection()方法中通过application参数轻松获取它。当然,这是推荐的方法,这样你永远不会出错。 - Hans Passant
谢谢提供的信息。但是我并没有创建任何插件,我正在创建一个 Visual Studio 包,在其中我想要打开特定实例的解决方案。 - Palak.Maheria
3个回答

24

这里有一系列的函数,可以让您枚举给定解决方案中的项目。以下是在当前解决方案中使用它的方法:

// get current solution
IVsSolution solution = (IVsSolution)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(IVsSolution));
foreach(Project project in GetProjects(solution))
{
    ....
}

....

public static IEnumerable<EnvDTE.Project> GetProjects(IVsSolution solution)
{
    foreach (IVsHierarchy hier in GetProjectsInSolution(solution))
    {
        EnvDTE.Project project = GetDTEProject(hier);
        if (project != null)
            yield return project;
    }
}

public static IEnumerable<IVsHierarchy> GetProjectsInSolution(IVsSolution solution)
{
    return GetProjectsInSolution(solution, __VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION);
}

public static IEnumerable<IVsHierarchy> GetProjectsInSolution(IVsSolution solution, __VSENUMPROJFLAGS flags)
{
    if (solution == null)
        yield break;

    IEnumHierarchies enumHierarchies;
    Guid guid = Guid.Empty;
    solution.GetProjectEnum((uint)flags, ref guid, out enumHierarchies);
    if (enumHierarchies == null)
        yield break;

    IVsHierarchy[] hierarchy = new IVsHierarchy[1];
    uint fetched;
    while (enumHierarchies.Next(1, hierarchy, out fetched) == VSConstants.S_OK && fetched == 1)
    {
        if (hierarchy.Length > 0 && hierarchy[0] != null)
            yield return hierarchy[0];
    }
}

public static EnvDTE.Project GetDTEProject(IVsHierarchy hierarchy)
{
    if (hierarchy == null)
        throw new ArgumentNullException("hierarchy");

    object obj;
    hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out obj);
    return obj as EnvDTE.Project;
}

2
你的方法在项目被包装到解决方案文件夹中时不起作用:http://stackoverflow.com/questions/33209589/project-names-in-visual-studio-solution-sometimes-are-empty - alerya

3

可能有更好的方法,但我简单尝试了一下,发现这个方法可行(它假设您知道解决方案名称)。根据这篇文章GetActiveObject不能保证获取当前实例的VS,所以你得到的结果是来自另一个实例。相反,您可以使用那里显示的GetDTE方法:

[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);

public static DTE GetDTE(int processId)
{
    string progId = "!VisualStudio.DTE.10.0:" + processId.ToString();
    object runningObject = null;

    IBindCtx bindCtx = null;
    IRunningObjectTable rot = null;
    IEnumMoniker enumMonikers = null;

    try
    {
        Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
        bindCtx.GetRunningObjectTable(out rot);
        rot.EnumRunning(out enumMonikers);

        IMoniker[] moniker = new IMoniker[1];
        IntPtr numberFetched = IntPtr.Zero;
        while (enumMonikers.Next(1, moniker, numberFetched) == 0)
        {
            IMoniker runningObjectMoniker = moniker[0];

            string name = null;

            try
            {
                if (runningObjectMoniker != null)
                {
                    runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
                }
            }
            catch (UnauthorizedAccessException)
            {
                // Do nothing, there is something in the ROT that we do not have access to.
            }

            if (!string.IsNullOrEmpty(name) && string.Equals(name, progId, StringComparison.Ordinal))
            {
                Marshal.ThrowExceptionForHR(rot.GetObject(runningObjectMoniker, out runningObject));
                break;
            }
        }
    }
    finally
    {
        if (enumMonikers != null)
        {
            Marshal.ReleaseComObject(enumMonikers);
        }

        if (rot != null)
        {
            Marshal.ReleaseComObject(rot);
        }

        if (bindCtx != null)
        {
            Marshal.ReleaseComObject(bindCtx);
        }
    }

    return (DTE)runningObject;
} 

如果您事先知道解决方案名称,则可以在进程MainWindowTitle属性中找到它,并将ProcessID传递给上面的方法。

var dte = GetDTE(System.Diagnostics.Process.GetProcesses().Where(x => x.MainWindowTitle.StartsWith("SolutionName") && x.ProcessName.Contains("devenv")).FirstOrDefault().Id);

虽然上面的代码可以工作,但我遇到了一个COM错误,通过使用这里显示的MessageFilter类进行修复。

从那篇文章中,这就是MessageFilter类的样子。

public class MessageFilter : IOleMessageFilter
{            
    // Class containing the IOleMessageFilter
    // thread error-handling functions.

    // Start the filter.
    public static void Register()
    {
        IOleMessageFilter newFilter = new MessageFilter();
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(newFilter, out oldFilter);
    }


    // Done with the filter, close it.
    public static void Revoke()
    {
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(null, out oldFilter);
    }


    //
    // IOleMessageFilter functions.
    // Handle incoming thread requests.
    int IOleMessageFilter.HandleInComingCall(int dwCallType,
    System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo)
    {

        //Return the flag SERVERCALL_ISHANDLED.
        return 0;
    }


    // Thread call was rejected, so try again.
    int IOleMessageFilter.RetryRejectedCall(System.IntPtr
    hTaskCallee, int dwTickCount, int dwRejectType)
    {

        if (dwRejectType == 2)
        // flag = SERVERCALL_RETRYLATER.
        {
            // Retry the thread call immediately if return >=0 &
            // <100.
            return 99;
        }
        // Too busy; cancel call.
        return -1;
    }


    int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee,
    int dwTickCount, int dwPendingType)
    {
        //Return the flag PENDINGMSG_WAITDEFPROCESS.
        return 2;
    }


    // Implement the IOleMessageFilter interface.
    [DllImport("Ole32.dll")]
    private static extern int
      CoRegisterMessageFilter(IOleMessageFilter newFilter, out
      IOleMessageFilter oldFilter);
}



[ComImport(), Guid("00000016-0000-0000-C000-000000000046"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
interface IOleMessageFilter
{
    [PreserveSig]
    int HandleInComingCall(
    int dwCallType,
    IntPtr hTaskCaller,
    int dwTickCount,
    IntPtr lpInterfaceInfo);

    [PreserveSig]
    int RetryRejectedCall(
    IntPtr hTaskCallee,
    int dwTickCount,
    int dwRejectType);


    [PreserveSig]
    int MessagePending(
        IntPtr hTaskCallee,
        int dwTickCount,
        int dwPendingType);
}

然后您可以像这样访问项目名称。
var dte = GetDTE(System.Diagnostics.Process.GetProcesses().Where(x => x.MainWindowTitle.StartsWith("SolutionName") && x.ProcessName.Contains("devenv")).FirstOrDefault().Id);
MessageFilter.Register();
var projects = dte.Solution.OfType<Project>().ToList();
MessageFilter.Revoke();

foreach (var proj in projects)
{
   Debug.WriteLine(proj.Name);
}

Marshal.ReleaseComObject(dte);

你好,感谢提供的解决方案,但它不能在VS 12版本上运行,我希望它能够适用于所有版本的Visual Studio。 - Palak.Maheria
3
无论如何,不是所有版本的Visual Studio都能正常工作。 - StingyJack

3

我相信你可以使用类似以下这样的内容:

var dte = (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE));
if (dte != null)
{
    var solution = dte.Solution;
    if (solution != null)
    {
        // get your projects here
    }
}

1
什么是“GetService()”? - Evgeni Nabokov
@EvgeniNabokov GetService是一个函数,如果您的VS包继承自特定类,则可能可用。我仍然没有找到那个类,但我已经发现ServiceProvider和Package.GetGlobalService()通常在从可扩展性项目项模板列表中添加命令、工具窗口或其他新项时可用。 - StingyJack
1
这个答案也存在同样的问题,即缺少组织在解决方案文件夹中的项目。solution.Projects 只返回顶层项目,而不包括其下面的任何内容。 - StingyJack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接