作为构建过程的一部分,在 C#/.NET 程序集中注入 C 代码

Tyr*_*rrz 5 .net c c# msbuild roslyn

我有一个 .NET Framework 3.5 项目,我想为其启用框架翻转,以便用户可以在安装了 .NET Framework 3.5 或 .NET Framework 4.x 运行时的系统上运行它。这应该允许用户通过从 Windows 7 开始的任何开箱即用的 Windows 安装来运行我的应用程序。

\n

我已经使用以下App.config文件进行了此工作:

\n
<?xml version="1.0" encoding="utf-8" ?>\n\n<!-- This configuration file is required by the apphost which runs on legacy .NET Framework for compatibility reasons -->\n\n<configuration>\n    <!-- Prefer .NET 3.5 (preinstalled on Windows 7), rollover to .NET 4.x (preinstalled on Windows 8+) -->\n    <startup useLegacyV2RuntimeActivationPolicy="true">\n        <supportedRuntime version="v2.0.50727" />\n        <supportedRuntime version="v4.0" />\n    </startup>\n</configuration>\n
Run Code Online (Sandbox Code Playgroud)\n

但是,我不想将配置文件与应用程序一起分发。我读过Michal Strehovsk\xc3\xbd 写的这篇很棒的文章,它解释了我可以通过在执行程序的入口点之前COMPLUS_OnlyUseLatestCLR=1设置环境变量来获得相同的行为。

\n

Michal 通过将 C# 代码编译为程序netmodule集而不是完整程序集,然后将结果与实现 TLS 回调的 C 程序集合并来实现此目的。该回调在程序执行时但在 .NET 运行时获取托管代码之前设置环境变量:

\n
#include <windows.h>\n\nVOID WINAPI tls_callback(\n    PVOID DllHandle,\n    DWORD Reason,\n    PVOID Reserved)\n{\n    if (Reason == DLL_PROCESS_ATTACH)\n        SetEnvironmentVariableW(L"COMPLUS_OnlyUseLatestCLR", L"1");\n}\n\n#ifdef _M_AMD64\n    #pragma comment (linker, "/INCLUDE:_tls_used")\n    #pragma comment (linker, "/INCLUDE:p_tls_callback")\n    #pragma const_seg(push)\n    #pragma const_seg(".CRT$XLAAA")\n        EXTERN_C const PIMAGE_TLS_CALLBACK p_tls_callback = tls_callback;\n    #pragma const_seg(pop)\n#endif\n#ifdef _M_IX86\n    #pragma comment (linker, "/INCLUDE:__tls_used")\n    #pragma comment (linker, "/INCLUDE:_p_tls_callback")\n    #pragma data_seg(push)\n    #pragma data_seg(".CRT$XLAAA")\n        EXTERN_C PIMAGE_TLS_CALLBACK p_tls_callback = tls_callback;\n    #pragma data_seg(pop)\n#endif\n
Run Code Online (Sandbox Code Playgroud)\n

我想实现他的方法,但使用 MSBuild 项目系统来完成尽可能多的繁重工作。

\n

在 Michal 的帖子中,他曾经csc将 C# 代码编译到 .NET 模块中。我尝试通过将项目的输出类型从更改为来复制它,Exe如下Module所示:

\n
<Project Sdk="Microsoft.NET.Sdk">\n\n  <PropertyGroup>\n    <TargetFramework>net35</TargetFramework>\n    <OutputType>module</OutputType>\n  </PropertyGroup>\n\n</Project>\n
Run Code Online (Sandbox Code Playgroud)\n

然而,这导致了我的第一个障碍——编译器产生了许多与 C# 的NRT 功能相关的错误:

\n
C:\\Users\\Tyrrr\\.nuget\\packages\\quickjson\\1.0.0\\contentFiles\\cs\\any\\QuickJson\\JsonNull.cs(6,23): error CS0518: Predefine\nd type \'System.Runtime.CompilerServices.NullableAttribute\' is not defined or imported [e:\\Projects\\Software\\DotnetRunti\nmeBootstrapper\\DotnetRuntimeBootstrapper.AppHost.Cli\\DotnetRuntimeBootstrapper.AppHost.Cli.csproj]\n\nC:\\Users\\Tyrrr\\.nuget\\packages\\quickjson\\1.0.0\\contentFiles\\cs\\any\\QuickJson\\JsonNull.cs(4,20): error CS0518: Predefine\nd type \'System.Runtime.CompilerServices.NullableContextAttribute\' is not defined or imported [e:\\Projects\\Software\\Dotn\netRuntimeBootstrapper\\DotnetRuntimeBootstrapper.AppHost.Cli\\DotnetRuntimeBootstrapper.AppHost.Cli.csproj]\n\ne:\\Projects\\Software\\DotnetRuntimeBootstrapper\\DotnetRuntimeBootstrapper.AppHost.Cli\\Utils\\ConsoleEx.cs(8,31): error CS\n0518: Predefined type \'System.Runtime.CompilerServices.NullableContextAttribute\' is not defined or imported [e:\\Project\ns\\Software\\DotnetRuntimeBootstrapper\\DotnetRuntimeBootstrapper.AppHost.Cli\\DotnetRuntimeBootstrapper.AppHost.Cli.csproj\n]\n\n...\n\n\n294 Error(s)\n
Run Code Online (Sandbox Code Playgroud)\n

我的项目确实使用了 NRT(它以最新版本的 C# 为目标,并使用 .NET 7 进行构建,尽管以 .NET Fx 3.5 运行时为目标),但它们是使用PolySharp 库进行多填充的。我还尝试使用Nullable polyfill 库,但它导致了同样的问题。

\n

设置<Nullable>disable</Nullable>也无法解决问题,因为我的代码包含 NRT 注释(即public MyClass? Foo())。有没有办法在不放弃 NRT 的情况下继续进行?

\n

我尝试谷歌搜索并在 Roslyn 存储库上发现了这个问题。.NET Core 似乎不正式支持编译为 .NET 模块。尽管如此,考虑到我的目标是 .NET Framework 并仅在 SDK 方面使用 .NET Core(csc两种方式都相同),我怀疑可能有一种方法可以破解构建系统以使其工作?

\n

最后,假设这个问题得到解决,编译 C 代码的最佳方法是什么?我可能可以将 C 文件作为<None>项目包含在我的项目结构中,并cl在之前的 MSBuild 目标中运行CoreBuild,模仿 Michal 所做的事情。但是,也许有一些内置方法可以实现相同的目的,也许可以通过引用 MSBuild 用于 C/C++ 工作负载的现有目标?

\n
\n

对于上下文,此问题与DotnetRuntimeBootstrapper上的此问题相关。

\n