【问题标题】:How do I programmatically find out the Action of each StartUp Project in a solution?如何以编程方式找出解决方案中每个启动项目的操作?
【发布时间】:2012-01-11 10:24:45
【问题描述】:

在Solution->Properties下,我可以设置多个启动项目:

我知道我可以获取标有“开始”的项目列表(通过使用 EnvDTE:solution.SolutionBuild.StartupProjects),但是如何获取操作为“不调试就开始”的项目列表?它们没有出现在列表中。

【问题讨论】:

标签: visual-studio vsx envdte visual-studio-sdk


【解决方案1】:

我不认为这是正式记录和提供的,但这里有一些信息:

  • 这是由 Visual Studio 内置包存储在 solution's .SUO file 中的。 SUO 文件具有 OLE 复合存储格式。您可以使用诸如OpenMCDF 之类的工具来浏览它(它有一个资源管理器示例)。在此文件中,您将看到一个名为“SolutionConfiguration”的流,其中包含一个 dwStartupOpt 令牌,后跟您要查找的信息。流本身具有自定义二进制格式。

  • 通过IVsPersistSolutionProps Interface 可从VS 中获得相同的信息。您需要从已加载的包之一中获取指向它的指针(例如,使用IVsShell.GetPackageEnum Method 枚举包列表。一个包将支持带有“SolutionConfiguration”流的 IVsPersistSolutionProps 接口。

    李>

但是,无论您选择哪种方法,我相信您最终都会手动解析“SolutionConfiguration”流。我在这里介绍了一种方法,它只需打开 SUO 文件并“手动”破解这些位,因此它可以在 VS 之外工作。

这是解析“SolutionConfiguration”流的实用程序类:

public sealed class StartupOptions
{
    private StartupOptions()
    {
    }

    public static IDictionary<Guid, int> ReadStartupOptions(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException("filePath");

        // look for this token in the file
        const string token = "dwStartupOpt\0=";
        byte[] tokenBytes = Encoding.Unicode.GetBytes(token);
        Dictionary<Guid, int> dic = new Dictionary<Guid, int>();
        byte[] bytes;
        using (MemoryStream stream = new MemoryStream())
        {
            CompoundFileUtilities.ExtractStream(filePath, "SolutionConfiguration", stream);
            bytes = stream.ToArray();
        }

        int i = 0;
        do
        {
            bool found = true;
            for (int j = 0; j < tokenBytes.Length; j++)
            {
                if (bytes[i + j] != tokenBytes[j])
                {
                    found = false;
                    break;
                }
            }
            if (found)
            {
                // back read the corresponding project guid
                // guid is formatted as {guid}
                // len to read is Guid length* 2 and there are two offset bytes between guid and startup options token
                byte[] guidBytes = new byte[38 * 2];
                Array.Copy(bytes, i - guidBytes.Length - 2, guidBytes, 0, guidBytes.Length);
                Guid guid = new Guid(Encoding.Unicode.GetString(guidBytes));

                // skip VT_I4
                int options = BitConverter.ToInt32(bytes, i + tokenBytes.Length + 2);
                dic[guid] = options;
            }
            i++;
        }
        while (i < bytes.Length);
        return dic;
    }
}

后跟一个小型复合流读取实用程序(无需外部库):

public static class CompoundFileUtilities
{
    public static void ExtractStream(string filePath, string streamName, string streamPath)
    {
        if (filePath == null)
            throw new ArgumentNullException("filePath");

        if (streamName == null)
            throw new ArgumentNullException("streamName");

        if (streamPath == null)
            throw new ArgumentNullException("streamPath");

        using (FileStream output = new FileStream(streamPath, FileMode.Create))
        {
            ExtractStream(filePath, streamName, output);
        }
    }

    public static void ExtractStream(string filePath, string streamName, Stream output)
    {
        if (filePath == null)
            throw new ArgumentNullException("filePath");

        if (streamName == null)
            throw new ArgumentNullException("streamName");

        if (output == null)
            throw new ArgumentNullException("output");

        IStorage storage;
        int hr = StgOpenStorage(filePath, null, STGM.READ | STGM.SHARE_DENY_WRITE, IntPtr.Zero, 0, out storage);
        if (hr != 0)
            throw new Win32Exception(hr);

        try
        {
            IStream stream;
            hr = storage.OpenStream(streamName, IntPtr.Zero, STGM.READ | STGM.SHARE_EXCLUSIVE, 0, out stream);
            if (hr != 0)
                throw new Win32Exception(hr);

            int read = 0;
            IntPtr readPtr = Marshal.AllocHGlobal(Marshal.SizeOf(read));
            try
            {
                byte[] bytes = new byte[0x1000];
                do
                {
                    stream.Read(bytes, bytes.Length, readPtr);
                    read = Marshal.ReadInt32(readPtr);
                    if (read == 0)
                        break;

                    output.Write(bytes, 0, read);
                }
                while(true);
            }
            finally
            {
                Marshal.FreeHGlobal(readPtr);
                Marshal.ReleaseComObject(stream);
            }
        }
        finally
        {
            Marshal.ReleaseComObject(storage);
        }
    }

    [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IStorage
    {
        void Unimplemented0();

        [PreserveSig]
        int OpenStream([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr reserved1, STGM grfMode, uint reserved2, out IStream ppstm);

        // other methods not declared for simplicity
    }

    [Flags]
    private enum STGM
    {
        READ = 0x00000000,
        SHARE_DENY_WRITE = 0x00000020,
        SHARE_EXCLUSIVE = 0x00000010,
        // other values not declared for simplicity
    }

    [DllImport("ole32.dll")]
    private static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IStorage pstgPriority, STGM grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstgOpen);
}

以及显示与启动选项相关的项目 guid 的示例:

static void SafeMain(string[] args)
{
    foreach (var kvp in StartupOptions.ReadStartupOptions("mySample.suo"))
    {
        if ((kvp.Value & 1) != 0)
        {
            Console.WriteLine("Project " + kvp.Key + " has option Start");
        }
        if ((kvp.Value & 2) != 0)
        {
            Console.WriteLine("Project " + kvp.Key + " has option Start with debugging");
        }
    }
}

【讨论】:

  • 谢谢!我尝试将其作为外部应用程序运行,它可以工作,但是如果我更改设置并再次运行它,我仍然会得到旧值 - 直到我关闭解决方案,此时似乎 VS 写入了 SUO,并且然后重新运行它给了我新的价值。使用 IVsPersistSolutionProps 会解决这个问题吗?如果没有,您是否知道强制 Visual Studio 写入 .suo 的任何方法?
  • 您需要保存解决方案以确保 suo 更改提交
  • 此解决方案适用于完整框架,但似乎不适用于紧凑框架。 BitConverter.ToInt32(bytes, i + tokenBytes.Length + 2) 对于所有的指导都是 0。任何解决方法可以使这项工作适用于紧凑的框架?
  • @user678229 - 你应该问另一个问题
  • @SimonMourier 我已经在这里发布了答案stackoverflow.com/questions/37618573/… 它基于您的解决方案,几乎没有变化。谢谢你的领导..
猜你喜欢
  • 1970-01-01
  • 2011-04-17
  • 1970-01-01
  • 1970-01-01
  • 2010-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多