我有一个包含 2 个项目的解决方案:
MSBuildQuestion.csproj
和 NativeLibrary.vcxproj
。
MSBuildQuestion.csproj
产生 MSBuildQuestion.exe
。 NativeLibrary.vcxproj
产生 NativeLibrary.dll
。
MSBuildQuestion.csproj
通过 NativeLibrary.vcxproj
引用 ProjectReference
,并通过 NativeLibrary.dll
将 CopyToOutputDirectory
复制到其输出:
<ItemGroup>
<ProjectReference Include="..\NativeLibrary\NativeLibrary.vcxproj" />
<Content Include="..\$(Platform)\$(Configuration)\*.dll">
<Link>NativeLibrary.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
当我清理(通过删除所有输出文件夹)然后通过 MSBuild 从命令行构建解决方案时,它不会将
NativeLibrary.dll
复制到 MSBuildQuestion.csproj
的输出,尽管 CopyToOutputDirectory
明确告诉它这样做。当我尝试运行时,这当然会导致“无法加载 DLL 'NativeLibrary.dll'”错误 MSBuildQuestion.exe
。
我是否遗漏了一些东西,而这不是应该从
*.csproj
引用本机DLL的方式?或者这是 MSBuild 中的一个错误?
这就是我清理、构建和运行解决方案的方式:
Get-ChildItem . -include bin,obj,x64 -Recurse | ForEach-Object ($_) { Remove-Item $_.FullName -Force -Recurse }
nuget restore
msbuild /p:Platform=x64 /p:Configuration=Release
.\MSBuildQuestion\bin\x64\Release\net6.0\MSBuildQuestion.exe
重现问题的项目是here。
一个观察:如果我运行
msbuild /p:Platform=x64 /p:Configuration=Release
两次,NativeLibrary.dll
会被复制,然后一切正常。就好像 MSBuild 违反了项目必须构建的顺序一样。虽然如果我检查 MSBuild 日志,它似乎并没有违反顺序,因为下一行是按预期顺序记录的:
Done Building Project "...\NativeLibrary.vcxproj"
Done Building Project "...\MSBuildQuestion.csproj"
Done Building Project "...\MSBuildQuestion.sln"
NativeLibrary.dll
不是依赖项托管代码是写入 .NET CLR(.NET 或 .NET Framework 实现)的代码。托管代码项目可以具有对其他托管代码程序集的编译依赖性。但本机代码程序集不能是编译时依赖项。托管代码调用本机代码的互操作性是在运行时实现的。
C# 项目可以有一组
ProjectReference
项。 ProjectReference
被解析为编译依赖项的程序集。本机程序集不能是编译依赖项。 NativeLibrary.dll
不是您的 C# 项目的依赖项,不是直接的,也不是传递性的。
NativeLibrary.dll
是代码中 DllImport
属性的依赖项,但那是在运行时。并且构建对运行时依赖一无所知。
项目的构建顺序由跨项目的
ProjectReference
项以及解决方案文件中设置的 Project Dependencies
确定。
在解决方案中设置项目依赖项只会影响构建顺序。它不提供参考或“依赖”。
MSBuild 在处理项目时有两个阶段:评估和执行。在评估阶段,“顶级”项目组得到解决。
MSBuildQuestion.csproj
包含:
<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup>
<ProjectReference Include="..\NativeLibrary\NativeLibrary.vcxproj" />
<Content Include="..\$(Platform)\$(Configuration)\*.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
在评估阶段,MSBuild 将查看并处理
ProjectReference
和 Content
。
ProjectReference
到 NativeLibrary.vcxproj
影响构建顺序。 MSBuild 将知道在 NativeLibrary.vcxproj
的执行阶段之前构建 MSBuildQuestion.csproj
。因为它是一个本机项目,所以没有可供编译的托管程序集。
Content
将被评估,除非有先前版本中的文件,否则不会匹配任何内容。
在执行阶段,有一个步骤会复制添加到
Content
的文件。除非先前版本留下的文件在评估阶段满足通配符,否则 NativeLibrary.dll
将不会出现在 Content
中。
(顺便说一句,如果您想要增量构建,请使用
PreserveNewest
而不是 Always
。)
要使此示例正常工作:
<ProjectReference Include="..\NativeLibrary\NativeLibrary.vcxproj" />
中删除 MSBuildQuestion.csproj
。MSBuildQuestion.csproj
具有对 NativeLibrary.vcxproj
的项目依赖。该解决方案将在
NativeLibrary.vcxproj
之前构建 MSBuildQuestion.csproj
,确保 MSBuildQuestion.csproj
的评估阶段将看到 NativeLibrary.vcxproj
的当前构建文件。 NativeLibrary.dll
将成为 Content
的一部分,并将被复制为 Content
的一部分。
在此示例中:
MSBuildQuestion2.csproj
构建依赖于 NativeLibraryWrapper.dll
的 .exe 程序集。NativeLibraryWrapper.csproj
构建一个 .dll 程序集,其 DllImport
为 NativeLibrary.dll
。NativeLibrary.vcxproj
构建本机 DLL NativeLibrary.dll
。当项目引用解析为对托管程序集的引用时,将复制传递托管依赖项。将复制与正在复制的程序集名称匹配的文件(例如 .pdb 文件)。
NativeLibrary.dll
这些都不是。
这可以像前面的例子一样解决:
MSBuildQuestion2.csproj
依赖于NativeLibrary.vcxproj
的项目依赖项。NativeLibrary.dll
添加为 Content
。是的,这并不干燥,而且很容易忘记。
另一种方法是让
NativeLibraryWrapper.csproj
发布文件夹或包。用托管代码编写的消费者将使用已发布的文件夹或包,而不是直接引用项目。