在解決方案 - >屬性,我可以設置多個啓動項目: 如何以編程方式查找解決方案中每個啓動項目的操作?
我知道,我能得到的標有「開始」的項目列表(通過使用EnvDTE:solution.SolutionBuild.StartupProjects
),但我怎麼獲取動作是「無需調試就開始」的項目列表?它們不會出現在列表中。
在解決方案 - >屬性,我可以設置多個啓動項目: 如何以編程方式查找解決方案中每個啓動項目的操作?
我知道,我能得到的標有「開始」的項目列表(通過使用EnvDTE:solution.SolutionBuild.StartupProjects
),但我怎麼獲取動作是「無需調試就開始」的項目列表?它們不會出現在列表中。
我不認爲這是記錄並提供正式,但這裏有一些信息:
這是通過在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的任何方式? – 2012-02-12 20:24:04
您需要保存解決方案以確保suo更改提交 – 2012-02-12 20:35:30
此解決方案適用於完整框架,但似乎不適用於緊湊型框架。對於所有的GUID,「BitConverter.ToInt32(字節,i + tokenBytes.Length + 2)」爲0。任何解決方法,使這項工作的緊湊框架? – user678229 2016-06-02 14:48:23
什麼是你的語言?你正在開發一個VS包嗎?一個VS Addin?或一些外部VS工具? – 2012-02-10 10:20:16
@SimonMourier在C#中編寫VS包# – 2012-02-10 15:24:17
我創建了一個[uservoice建議將其添加到官方API](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/7555128-extend-visual -studio-api-with-a-method-to-set-star) – 2015-04-13 18:22:58