经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » Visual Studio » 查看文章
dotnet Roslyn 通过读取 suo 文件获取解决方案的启动项目
来源:cnblogs  作者:lindexi  时间:2021/5/10 9:07:03  对本文有异议

本文来告诉大家一个黑科技,通过 .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 编码的字符串,读取的方法如下

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

这里的 text 的内容大概如下

  1. "\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 值,以下是我写的正则

  1. var text = encoding.GetEncoding().GetString(byteList);
  2. const char nul = '\u0000';
  3. const char dc1 = '\u0011';
  4. const char etx = '\u0003';
  5. const char soh = '\u0001';
  6. var startupProjectRegex = new Regex(@$"StartupProject{nul}={'\b'}&{nul}(.{'{'}{38}{'}'});A");
  7. var startupProjectMatch = startupProjectRegex.Match(text);
  8. if (startupProjectMatch.Success)
  9. {
  10. var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
  11. }

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

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

代码如下

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

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

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

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

  1. private static FileInfo GetStartupProject(FileInfo solutionFile)
  2. {
  3. var solutionFilePath = solutionFile.FullName;
  4. var solutionDirectory = solutionFile.DirectoryName;
  5. var solutionName = Path.GetFileNameWithoutExtension(solutionFilePath);
  6. var suoDirectoryPath = Path.Combine(solutionDirectory, ".vs", solutionName, "v16");
  7. Directory.CreateDirectory(suoDirectoryPath);
  8. var suoFilePath = Path.Combine(suoDirectoryPath, ".suo");
  9. var projectList = SetStartupProjects.SolutionProjectExtractor.GetAllProjectFiles(solutionFile.FullName).ToList();
  10. using (var fileStream = new FileStream(suoFilePath, FileMode.Open))
  11. {
  12. using CompoundFile compoundFile = new CompoundFile(fileStream, CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors);
  13. var cfStream = compoundFile.RootStorage.GetStream("SolutionConfiguration");
  14. var byteList = cfStream.GetData();
  15. var encoding = Encoding.GetEncodings()
  16. .Single(x => string.Equals(x.Name, "utf-16", StringComparison.OrdinalIgnoreCase));
  17. var text = encoding.GetEncoding().GetString(byteList);
  18. const char nul = '\u0000';
  19. const char dc1 = '\u0011';
  20. const char etx = '\u0003';
  21. const char soh = '\u0001';
  22. var startupProjectRegex = new Regex(@$"StartupProject{nul}={'\b'}&{nul}(.{'{'}{38}{'}'});A");
  23. var startupProjectMatch = startupProjectRegex.Match(text);
  24. if (startupProjectMatch.Success)
  25. {
  26. var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
  27. var project = projectList.FirstOrDefault(temp => new Guid(temp.Guid) == guid);
  28. return new FileInfo(project.FullPath);
  29. }
  30. }
  31. return null;
  32. }

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

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

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

  1. compoundFile.RootStorage.VisitEntries(item =>
  2. {
  3. if (item.IsStream)
  4. {
  5. Console.WriteLine(item.Name);
  6. var stream = item as CFStream;
  7. byteList = stream.GetData();
  8. text = encoding.GetEncoding().GetString(byteList);
  9. }
  10. }, true);

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

更多请看

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

Solution User Options (.Suo) File

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

原文链接:http://www.cnblogs.com/lindexi/p/14742472.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号