Roslyn 解决 dotnet core 应用进程间引用找不到 runtimeconfig 依赖文件
我有一个强大的功能,这个功能就是在 Linux 下使用 GDI 转换 EMF 格式图片为 png 图片,但是有一些有趣的图片会让转换的进程炸掉。因此我就想让转换服务放在独立的进程,通过进程间调用,也就是命令行调用传入参数的方式,让另一个进程转换图片。而此时就会遇到一个问题,如何让这个进程也被构建,然后输出到输出路径
在 .NET Core 里面,如果想要让输出文件夹包含两个不同的进程入口文件,最简单的方法是让一个项目引用另一个项目。这个做法在 .NET Framework 里面很好用,因为此时将会在输出文件夹里面包含两个项目的输出文件。也就解决了如果让另一个进程也被构建的问题
不过在 dotnet core 里面将会存在一个文件,如果项目引用了一个输出为 exe 的项目,此时想要让这个可执行程序运行,将会遇到这样的坑,在 .NET Core 里面规定了可执行程序需要有两个配置文件,而默认项目引用将会缺少这两个配置文件
- .deps.json
- .runtimeconfig.json
在默认构建一个可执行程序,如 exe 程序的 .NET Core 项目,将可以在输出路径看到 xx.deps.json 文件和 xx.runtimeconfig.json 两个文件。如果没有这两个文件会如何?在运行可执行程序将会提示下面代码
A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in 'C:
Failed to run as a self-contained app. If this should be a framework-dependent app, add the C:
提示找不到 The library 'hostpolicy.dll' required to execute the application was not found
的原因就是因为不存在 runtimeconfig.json 文件
那么这两个文件的作用是什么,请看 深入理解.NET Core的基元: deps.json, runtimeconfig.json, dll文件 - LamondLu - 博客园
而如果我单个项目构建的时候,其实是可以在项目输出文件夹看到这两个配置文件。但是如果被引用了,那么将找不到这两个文件
解决方法就是在被引用的项目的 csproj 文件里面添加如下代码
<Target Name="AddRuntimeDependenciesToContent" Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp'" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles" >
<ItemGroup>
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
<ContentWithTargetPath Include="$(ProjectRuntimeConfigFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectRuntimeConfigFileName)" />
</ItemGroup>
</Target>
上面代码的含义是什么?其实就是将这两个配置文件加入到 ContentWithTargetPath
项,将会被其他项目放在输出文件夹里面
上面代码的 AddRuntimeDependenciesToContent
是一个随意的名字,小伙伴可以根据自己的需求随意更改
而核心的逻辑是在 BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles"
这里
在 BeforeTargets 也就是说明这个 Target 在构建过程的执行时机是需要在 GetCopyToOutputDirectoryItems
执行之前,这个 GetCopyToOutputDirectoryItems
就是决定有哪些内容将会需要输出到最终输出文件夹里面。因此需要在他之前之前,给他设置需要输出的内容
然后在 DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles"
是因为在 GenerateBuildDependencyFile
之后才输出 .deps.json 文件,在 GenerateBuildRuntimeConfigurationFiles
才输出 .runtimeconfig.json 文件
如果没有写 DependsOnTargets 那么将会构建提示 error MSB3030
找不到复制文件
error MSB3030: 无法复制文件“C:lindexibinReleasenetcoreapp3.1lindexi.runtimeconfig.json”,原因是找不到该文件
而如果后续你觉得这个原本是输出为可执行文件的进程不想继续作为输出为 exe 了,将 OutputType 修改为库,那么请记得删除上面的代码,因此此时的输出里面将没有包含配置文件
更多关于 Roslyn 请看 手把手教你写 Roslyn 修改编译
如果不想使用引用项目的方法,还有其他方法可以做到,让多个项目没有依赖,但是都能构建。请看 三种方法设置 .NET/C# 项目的编译顺序,而不影响项目之间的引用 - walterlv
本文的方法存在的不足是,如果最后是作为框架依赖发布的,那么也许会遇到这样的问题,实际安装的库被作为框架的一部分,此时引用路径将会不相同。构建的项目里面依赖的是本地的 lib 文件的路径,而框架发布的项目使用的是 ref 的文件夹路径。如 GDI 库的实现里面,在运行的时候将会看到输出是找不到
dotnet ImageOptimizationProcess.dll
Error:
An assembly specified in the application dependencies manifest (ImageOptimizationProcess.deps.json) was not found:
package: 'Microsoft.Win32.SystemEvents', version: '4.7.0'
path: 'lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll'
原因是可执行项目输出的配置文件内容如下
"System.Drawing.Common/4.7.0":
{
"dependencies":
{
"Microsoft.NETCore.Platforms": "3.1.0",
"Microsoft.Win32.SystemEvents": "4.7.0"
},
"runtime":
{
"lib/netstandard2.0/System.Drawing.Common.dll":
{
"assemblyVersion": "4.0.0.1",
"fileVersion": "4.6.26919.2"
}
},
"runtimeTargets":
{
"runtimes/unix/lib/netcoreapp3.0/System.Drawing.Common.dll":
{
"rid": "unix",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
},
"runtimes/win/lib/netcoreapp3.0/System.Drawing.Common.dll":
{
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
}
}
但是实际的 GDI 的库是作为框架共享的,放在 /usr/share/dotnet/shared/Microsoft.AspNetCore.App/3.1.6/System.Drawing.Common.dll
路径,因此找不到库,进程执行失败
- 一步步实现静态页面布局
- Stanford机器学习笔记-3.Bayesian statistics and Regularization
- 在R中使用支持向量机(SVM)进行数据挖掘
- 【你问我答】你与Java大牛的距离,只差这24个问题
- Android漏洞扫描工具Code Arbiter
- Huawei HG532 系列路由器远程命令执行漏洞分析
- postMessage与postMessage跨域
- 【手把手教你做项目】自然语言处理:单词抽取/统计
- D-Link系列路由器漏洞挖掘入门
- 大家一直在谈的领域驱动设计(DDD),我们在互联网业务系统是这么实践的
- 在Atom中设置Python开发环境
- Kaggle赛题解析:逻辑回归预测模型实现
- Shield:支撑美团点评品类最丰富业务的移动端模块化框架开源了
- 点击块,让小块动起来 - 函数封装
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- flask SQLAlchemy查询数据库最近30天,一个月,一周,12小时或之前的数据
- TypeScript是什么,为什么要使用它?
- Python 基础(十):模块与包
- 谷歌浏览器一些https打不开点击高级不行的解决办法
- CV入门赛最全思路&上分技巧汇总!
- VBA专题10-5:使用VBA操控Excel界面之隐藏/取消隐藏及最小化功能区
- 从一道面试题说起:GET 请求能传图片吗?
- ModuleNotFoundError: No module named ‘pip‘【已解决】
- 【原创】Java并发编程系列31 | 阻塞队列(上)
- 【2019-3-3】Mac启动redis
- awvs(acunetix)使用一段时间后突然不能用了-解决方案
- Java自动化测试(数据库断言 18)
- Java自动化测试(参数化 19)
- Python 基础(一):入门必备知识
- Mac安装软件提示 已损坏【已解决】