本文来告诉大家一个黑科技,通过 .suo 文件读取 VisualStudio 的启动项目。在 sln 项目里面,都会生成对应的 suo 文件,这个文件是 OLE 格式的文件,文件的格式没有公开,本文的方法适合用在 VisualStudio 2019 上,对于其他版本的 VisualStudio 也许会不适合

感谢 Simon Cropp 大佬提供的方法

默认在 sln 解决方案文件的相同文件夹里面,将会存放 .vs\{解决方案名}\v{VS版本}\.suo 文件,如解决方案文件名为 HairhechallchujurKairbilairlem.sln 在 VisualStudio 2019 下将会存放 .vs\HairhechallchujurKairbilairlem\v16\.suo 文件

这个 .suo 文件是包含了 VisualStudio 解决方案的一些配置,如启动项目。关多关于此文件,请参阅 Solution User Options (.Suo) File 文档

预计这个 suo 格式文件基本不会更改,在 1995 年的时候就开始使用这个格式

读取 .suo 需要使用到 Open MCDF 库。这是一个完全由 C# 实现的读取 OLE 格式文档的库,我在做 OFFICE 组件也用到这个库

在 suo 文件里面,通过 SolutionConfiguration 内容存放当前的启动项,这里面的内容是使用 UTF-16 编码的字符串,读取的方法如下

            using (var fileStream = new FileStream(suoFilePath, FileMode.Open))
{
using CompoundFile compoundFile = new CompoundFile(fileStream, CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors);
var cfStream = compoundFile.RootStorage.GetStream("SolutionConfiguration");
var byteList = cfStream.GetData();
var encoding = Encoding.GetEncodings()
.Single(x => string.Equals(x.Name, "utf-16", StringComparison.OrdinalIgnoreCase));
var text = encoding.GetEncoding().GetString(byteList);
}

这里的 text 的内容大概如下

"\u0011\0MultiStartupProj\0=\u0003\0\0;4\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B}.dwStartupOpt\0=\u0003\0\0;\u000f\0StartupProject\0=\b&\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B};A\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B}.Release|Any CPU.fBatchBld\0=\u0003\0\0;?\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B}.Debug|Any CPU.fBatchBld\0=\u0003\0\0;4\0{AE3577E5-5D4E-44F8-B181-88A31B92584A}.dwStartupOpt\0=\u0003\0\0;A\0{AE3577E5-5D4E-44F8-B181-88A31B92584A}.Release|Any CPU.fBatchBld\0=\u0003\0\0;?\0{AE3577E5-5D4E-44F8-B181-88A31B92584A}.Debug|Any CPU.fBatchBld\0=\u0003\0\0;4\0{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}.dwStartupOpt\0=\u0003\0\0;\n\0ActiveCfg\0=\b\r\0Debug|Any CPU;"

通过读取 StartupProject 后续的内容即可找到当前的启动项目的 GUID 值,以下是我写的正则

                var text = encoding.GetEncoding().GetString(byteList);

                const char nul = '\u0000';
const char dc1 = '\u0011';
const char etx = '\u0003';
const char soh = '\u0001'; var startupProjectRegex = new Regex(@$"StartupProject{nul}={'\b'}&{nul}(.{'{'}{38}{'}'});A");
var startupProjectMatch = startupProjectRegex.Match(text);
if (startupProjectMatch.Success)
{
var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
}

上面代码拿到的 guid 就是启动项目的 guid 内容

咱可以采用 Simon Cropp 大佬的开源项目 https://github.com/SimonCropp/SetStartupProjects 来辅助读取当前 sln 里面包含的 csproj 的 GUID 和路径

代码如下

var projectList = SetStartupProjects.SolutionProjectExtractor.GetAllProjectFiles(solutionFile.FullName).ToList();

通过 guid 获取当前的 csproj 项目文件路径方法如下

                    var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
var project = projectList.FirstOrDefault(temp => new Guid(temp.Guid) == guid);

我封装了方法,传入的是 sln 文件,返回启动项目的路径

        private static FileInfo GetStartupProject(FileInfo solutionFile)
{
var solutionFilePath = solutionFile.FullName;
var solutionDirectory = solutionFile.DirectoryName; var solutionName = Path.GetFileNameWithoutExtension(solutionFilePath);
var suoDirectoryPath = Path.Combine(solutionDirectory, ".vs", solutionName, "v16");
Directory.CreateDirectory(suoDirectoryPath);
var suoFilePath = Path.Combine(suoDirectoryPath, ".suo"); var projectList = SetStartupProjects.SolutionProjectExtractor.GetAllProjectFiles(solutionFile.FullName).ToList();
using (var fileStream = new FileStream(suoFilePath, FileMode.Open))
{
using CompoundFile compoundFile = new CompoundFile(fileStream, CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors);
var cfStream = compoundFile.RootStorage.GetStream("SolutionConfiguration");
var byteList = cfStream.GetData();
var encoding = Encoding.GetEncodings()
.Single(x => string.Equals(x.Name, "utf-16", StringComparison.OrdinalIgnoreCase));
var text = encoding.GetEncoding().GetString(byteList); const char nul = '\u0000';
const char dc1 = '\u0011';
const char etx = '\u0003';
const char soh = '\u0001'; var startupProjectRegex = new Regex(@$"StartupProject{nul}={'\b'}&{nul}(.{'{'}{38}{'}'});A");
var startupProjectMatch = startupProjectRegex.Match(text);
if (startupProjectMatch.Success)
{
var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
var project = projectList.FirstOrDefault(temp => new Guid(temp.Guid) == guid);
return new FileInfo(project.FullPath);
}
} return null;
}

需要先在项目安装 SetStartupProjects 库,才能使用这个方法

本文所有代码放在 githubgitee 欢迎小伙伴访问

除了读取启动项目,还可以读取断点等内容,读取 suo 里面的所有内容的方法如下

                compoundFile.RootStorage.VisitEntries(item =>
{
if (item.IsStream)
{
Console.WriteLine(item.Name); var stream = item as CFStream;
byteList = stream.GetData();
text = encoding.GetEncoding().GetString(byteList);
}
}, true);

当然了,获取到的内容不一定使用 UTF-16 编码格式,还需要自己尝试,里面的数据只是二进制而是,上面代码的转换字符串只是用来调试

更多请看

SimonCropp/SetStartupProjects: Setting Visual Studio startup projects by hacking the suo

Solution User Options (.Suo) File

更多编译相关请看手把手教你写 Roslyn 修改编译

dotnet Roslyn 通过读取 suo 文件获取解决方案的启动项目的更多相关文章

  1. Java 读取网络资源文件 获取文件大小 MD5校验值

    Java 读取网络资源文件 获取文件大小 MD5校验值 封装一个文件操作工具类: package c; import java.io.*; import java.net.HttpURLConnect ...

  2. [获取行数]php读取大文件提供性能的方法,PHP的stream_get_line函数读取大文件获取文件的行数的方...

    背景: 下面是获取文件的行数的方法: 一个文件如果知道有几行的话,就可以控制获取一定的行数的数据,然后放入数据库.这样不管的读取大文件的性能,还是写入数据库的性能,都能得到很大的提高了. 下面是获取文 ...

  3. 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂

    浅谈JS中的!=.== .!==.===的用法和区别   var num = 1;     var str = '1';     var test = 1;     test == num  //tr ...

  4. 强悍的Python读取大文件的解决方案

    这是一道著名的 Python 面试题,考察的问题是,Python 读取大文件和一般规模的文件时的区别,也即哪些接口不适合读取大文件. 1. read() 接口的问题 f =open(filename, ...

  5. 基于.NET的程序读取Excel文件的解决方案

    目录 0. 前言 1. 使用NPOI库读取Excel文件 2. 使用OleDbConnection 3. 相关参考 shanzm-2020年12月8日 23:48:11 0. 前言 以前基于 .NET ...

  6. java通过读取本地文件获取反射方法参数,执行对象方法

    运用到的知识点 IO流, 集合properties   反射 在工程目录下新建file   config.properties #one time only can run one method cl ...

  7. C#读取DLL文件获取所有类

    说明 调用Web.dll 文件,获取其中的所有的WebService 参考 https://blog.csdn.net/huoliya12/article/details/78873123 流程 使用 ...

  8. 读取 xml 文件 获取其中保存的数据信息

    建立一个存储过程来返回要读取的数据形成结果集: CREATE PROC dbo.getValuesFromXmlByPath@fileName NVARCHAR(128)asDECLARE @T XM ...

  9. Mybatis 的 xml 文件语法错误,启动项目时控制台一直循环解析但是不打印错误

    重写SqlSessionFactoryBean的buildSqlSessionFactory方法: eg: package com.slp; import java.io.IOException; i ...

  10. Maven01-maven打包Web项目成war文件-tomcat脱机运行启动项目

    1 执行package 2 复制 3 catalina run  ,打开cmd窗口 4 输入网址 5注意要配置tomcat的 Application context为工程名字

随机推荐

  1. Java 22正式发布,一文了解全部新特性

    就在昨晚,Java 22正式发布!该版本提供了 12 项功能增强,其中包括 7 项预览功能和 1 项孵化器功能.它们涵盖了对 Java 语言.API.性能以及 JDK 中包含的工具的改进. 下面就来一 ...

  2. 使用SpringBatch读取csv文件

    目录 1.需求 2.解决方案 3.注意事项 1.文件路径的获取 2.各个Step如果获取到ExecutionContext中的值 3.FlatFileItemReader使用注意 4.实现步骤 1.导 ...

  3. rnacos版本更新为 v0.1.5

    rnacos是一个用 rust重新实现的nacos. rnacos比java实现的nacos更轻量.快速.稳定:合适在开发.测试.受资限服务等环境平替nacos服务使用. rnacos v0.1.5 ...

  4. 学习笔记-Kafka消息队列

    官网地址:https://kafka.apache.org/ 一.认识kafka 1.认识kafka Apache Kafka是Apache软件基金会的开源的流处理平台,该平台提供了消息的订阅与发布的 ...

  5. WebRTC开源框架

    1.什么是WebRTC WebRTC (Web Real-Time Communications)是一项实时通讯的技术,旨在使得浏览器能为实时通信提供简单的JavaScript接口,让浏览器通过JS实 ...

  6. #线段树#洛谷 4340 [SHOI2016]随机序列

    题目 分析 可以发现加号和减号会抵消掉,真正有用的答案就是第一段的乘积. 那也就是 \(\sum_{i=1}^nS_i*2*3^{n-i-1}\),其中 \(S_i\) 表示 \(a_1\) 到 \( ...

  7. USACO 4.2

    目录 洛谷 2740 草地排水 代码(网络最大流) 洛谷 2751 工序安排 分析 代码 洛谷 1894 完美的牛栏 代码(二分图最大匹配) 草地排水洛谷传送门,草地排水USACO传送门 工序安排洛谷 ...

  8. #Multi-SG#HDU 5795 A Simple Nim

    题目 有\(n\)堆石子,每次可以从一堆中取出若干个或是将一堆分成三堆非空的石子, 取完最后一颗石子获胜,问先手是否必胜 分析 它的后继还包含了分成三堆非空石子的SG函数,找规律可以发现 \[SG[x ...

  9. #区间dp,离散#D 弱者对决

    分析 设\(dp[i][j][x]\)表示当前区间为\([i,j]\),最小值为\(x\)的最大总分, 状态转移方程可以用后缀最大值优化到\(O(n^3m)\),主要难点是输出方案 后缀最大值需要记录 ...

  10. OpenHarmony 3.1 Release版本关键特性解析——HDI硬件设备接口介绍

    HDF 驱动框架是 OpenAtom OpenHarmony(简称"OpenHarmony")系统硬件生态开放的基础,为驱动开发者提供了驱动加载.驱动服务管理和驱动消息机制等驱动框 ...