【问题标题】:Custom Is64BitOperatingSystem preprocessor directive自定义 Is64BitOperatingSystem 预处理器指令
【发布时间】:2020-04-07 15:04:12
【问题描述】:

我们可以像这样为Platform Conditional Compilation in .NET Core 添加自定义预处理器指令

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
    <IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
    <IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
  </PropertyGroup>
  <PropertyGroup Condition="'$(IsWindows)'=='true'">
    <DefineConstants>Windows</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition="'$(IsOSX)'=='true'">
    <DefineConstants>OSX</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition="'$(IsLinux)'=='true'">
    <DefineConstants>Linux</DefineConstants>
  </PropertyGroup>
</Project>

我已经测试过了,它工作正常。

现在我想检测我是否在 64 位操作系统上。这是我的.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <Is64BitOperatingSystem Condition="'$([System.Environment]::Is64BitOperatingSystem)' == 'true'">true</Is64BitOperatingSystem>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Is64BitOperatingSystem)'=='true'">
    <DefineConstants>Is64BitOperatingSystem</DefineConstants>
  </PropertyGroup>
</Project>

但是,当我运行此代码时,我的第一个 if...else 正在按预期工作,但我的 Is64BitOperatingSystem 预处理器指令却没有

if (System.Environment.Is64BitOperatingSystem)
    Console.WriteLine(64);
else
    Console.WriteLine(32);

#if Is64BitOperatingSystem
    Console.WriteLine(64);
#else
   Console.WriteLine(32);
#endif

我做错了什么?我无法发现我的代码中的错误在哪里。

谢谢

编辑

为了添加更多详细信息,我将此代码包含在一个 .NET Core 项目调用的 .NET Standard 库中。

我希望我的库能够检测到它正在运行的当前架构(或已为其编译),以便我可以执行类似的操作

#if Is64BitOperatingSystem
    [DllImport(@"Resources/HIDAPI/x64/hidapi")]
#else
    [DllImport(@"Resources/HIDAPI/x32/hidapi")]
#endif

在调试之前,Visual Studio 显然会编译我的应用程序,因此在此阶段使用 System.Environment.Is64BitOperatingSystem 或预处理器指令检查架构应该会给出相同的结果,但事实并非如此。我在 64 位机器上,我的预处理器指令告诉我我在 32 位架构上,即使我在 Visual Studio 配置管理器中将 AnyCPU 更改为 x64

请注意,this answer 是特定于 Windows 的,that one 也是因为解决方案是从 kernel32.dll 调用 SetDllDirectory 函数

但我希望我的代码能够在 Linux 上运行。

编辑 2:

为了在这里分享一个最小的示例,我实际上删除了代码中的错误部分。

看起来这给出了预期的结果:

<PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <Is64BitOperatingSystem Condition="'$([System.Environment]::Is64BitOperatingSystem)' == 'true'">true</Is64BitOperatingSystem>
</PropertyGroup>

<PropertyGroup Condition="'$(Is64BitOperatingSystem)'=='true'">
    <DefineConstants>Is64BitOperatingSystem</DefineConstants>
</PropertyGroup>

但这会产生错误的行为:

<PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <Is64BitOperatingSystem Condition="'$([System.Environment]::Is64BitOperatingSystem)' == 'true'">true</Is64BitOperatingSystem>
    <IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
</PropertyGroup>

<PropertyGroup Condition="'$(Is64BitOperatingSystem)'=='true'">
    <DefineConstants>Is64BitOperatingSystem</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(IsWindows)'=='true'">
    <DefineConstants>Windows</DefineConstants>
</PropertyGroup>

如果有人可以解释一下吗? 我不明白为什么添加 IsWindows 条件会导致 Is64BitOperatingSystem 预处理器指令上的不同行为

【问题讨论】:

  • 当您说它不起作用时,请解释您的意思。你实际上在做什么来尝试这个? if (System.Environment.Is64BitOperatingSystem)#if Is64BitOperatingSystem 在这里表示非常不同的东西。第一个取决于代码的运行位置。第二个取决于代码在哪里编译
  • 所以这实际上不是您的 .csproj 文件,您发布的内容已损坏。
  • @HansPassant 我发布的内容没有损坏,第一个代码块是我链接的博客文章的引用,因为这是 SO 的一个好习惯。我为第二个代码块写了Here is my .csproj
  • @JérômeMEVEL 您的csproj 文件中有多余的关闭&lt;/PropertyGroup&gt; 标记。我也无法重现您的问题,Is64BitOperatingSystem 按预期工作
  • @PavelAnikhouski 哎呀,这只是一个复制粘贴问题。现在我的 .NET Standard 库和我的 .NET Core 项目都有&lt;Platforms&gt;x64&lt;/Platforms&gt;,我重新编译了我的代码,但我仍然遇到问题

标签: c# .net-core msbuild .net-standard preprocessor-directive


【解决方案1】:

它在上次编辑中不起作用的原因是DefineConstants 中的条件常量不能单独定义,该属性的值是分号分隔的值列表,应该通过添加一个新常量来定义现有列表(非常感谢@Orace 的帮助)

<PropertyGroup Condition="'$(IsWindows)'=='true'">
    <DefineConstants>$(DefineConstants);Windows</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(Is64BitOperatingSystem)'=='true'">
    <DefineConstants>$(DefineConstants);Is64BitOperatingSystem</DefineConstants>
</PropertyGroup>

您也可以将这些值传递给msbuild 命令行。

以下代码将按预期工作

#if Windows
    Console.WriteLine("built in Windows!");
#endif

#if Is64BitOperatingSystem
    Console.WriteLine("built on x64");
#else
    Console.WriteLine("built on x86");
#endif
    Console.WriteLine(Environment.Is64BitOperatingSystem ? "running on x64" : "running on x86");

它将显示:

built in Windows! 
built on x64
running on x64

您也可以以同样的方式添加Linux 常量。

以下 msbuild 目标将帮助您检查项目中定义了哪些常量

<Target BeforeTargets="Build" Name="test">
    <Message Importance="High" Text="$(DefineConstants)"/>
</Target>

在我的测试应用中,它显示 TRACE;Windows;Is64BitOperatingSystem;DEBUG;NETCOREAPP;NETCOREAPP2_1

【讨论】:

  • 有一些解决方法可以避免完全覆盖defineconstants 内容。见:stackoverflow.com/questions/22112684/…
  • 也许只需在DefineConstants 中添加$(DefineConstants);...
  • @Orace 谢谢,这是有道理的。稍后会检查并更新答案
  • @JérômeMEVEL 能解决您的问题吗?您需要更多详细信息/解释吗?
  • @Pavel Anikhouski 非常感谢您的回答。尽管它对我有很大帮助,但它并没有完全解决我的问题。我给自己写了一个答案
【解决方案2】:

我希望我的库能够检测到它正在运行(或已为其编译)的当前架构,以便我可以执行类似的操作

如果这是您的要求,那么我认为您做事的方式不对。您尝试使用的方法将使库依赖于架构构建它的位置,仅此而已。

编译指令,顾名思义,在编译时生效。它们在运行时无效。如果您想为不同的架构使用不同的[DllImport]s,您将需要不同的构建。通常情况下,解决这个问题的方法是使用不同的构建配置。

在配置管理器中,创建两个独立的解决方案平台,以及对应的项目平台:

在您的项目属性 -> 构建选项卡中,为两者之一指定条件编译指令:

在您的代码中,使用编译符号:

#if Is64Bit
    [DllImport(@"Resources/HIDAPI/x64/hidapi")]
#else
    [DllImport(@"Resources/HIDAPI/x32/hidapi")]
#endif

【讨论】:

  • 我必须说,即使在多次阅读您的答案后,我仍然对此感到困惑。 The approach you are trying to use will make the library dependent on the architecture where it was built and nothing else 但是你建议我有几个 build configurations 代替......你是说我的方式吗,即使我尝试为 32 位架构编译它仍然会将 Is64BitOperatingSystem 视为 true 因为我编译了它在 64 位机器上?你能解释一下DefineConstantsConditional compilation symbol之间的区别吗?谢谢
  • @JérômeMEVEL “你是说我的方式,即使我尝试为 32 位架构编译它仍然会认为 Is64BitOperatingSystem 是真的,因为我在 64 位机器上编译它?” 是的,AFAIK Environment.Is64BitOperatingSystem 返回实际操作系统的位数,而不是程序集的位数。随意尝试自己并验证,但这就是我发现的。因此,为了使用您正在尝试的方法,您必须在 32 位操作系统上实际构建您的程序才能进行 32 位构建。使用上述方法,您可以在一台机器上构建两者。
  • @JérômeMEVEL “能否请您多解释一下 DefineConstants 和条件编译符号之间的区别?” 它们本质上是一回事。 “条件编译符号”只是项目属性窗格中给它们的名称。
  • 感谢您的回答,这对我很有帮助,就像Pavel answer's。我赞成,但您使用 Visual Studio 界面的解释不够清楚 IMO。我写了自己的答案,用一些.csproj 示例来澄清一切
【解决方案3】:

我要感谢 JLRishePavel Anikhouski 的回答。尽管它们非常有用,但它们实际上都不完整,所以我想我应该给自己写一个包含所有必要信息的答案。

我创建了一个sample project on Github,以便您可以使用它。以下是解释

在 Visual Studio 配置管理器中添加 Conditional Compilation Symbol 实际上会在后台的 .csproj 中添加一个 DefineConstants 节点。

它不需要任何新的 MsBuild 变量声明,就像我在我的问题中所做的那样。它只是使用一个已经存在的现有Platform 变量。

此外,我在问题中使用的方式似乎与 dotnet publish 命令不兼容

像这样的.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm</RuntimeIdentifiers>
    <Is64BitOperatingSystem Condition="'$([System.Environment]::Is64BitOperatingSystem)' == 'true'">true</Is64BitOperatingSystem>
    <Platforms>x64;x86;arm64;arm86</Platforms>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Is64BitOperatingSystem)'=='true'">
    <DefineConstants>$(DefineConstants);Is64BitOperatingSystem</DefineConstants>
  </PropertyGroup>

</Project>

运行命令

dotnet publish -r win-x64 -c Release

会返回以下错误

DetectArchitectureSample.csproj(7,29):错误 MSB4185:类型“System.Environment”的函数“Is64BitOperatingSystem”不可作为 MSBuild 属性函数执行。

我们实际上不需要添加额外的Is64BitOperatingSystem 变量。我们只需要像这样重用现有的Platform 变量

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm</RuntimeIdentifiers>
    <Platforms>x64;x86;arm64;arm86</Platforms>
  </PropertyGroup>

  <PropertyGroup Condition="$(Platform)=='x64' Or $(Platform)=='arm64'">
    <DefineConstants>$(DefineConstants);Is64Bit</DefineConstants>
  </PropertyGroup>

  <Target BeforeTargets="Build" Name="test">
    <Message Importance="High" Text="$(DefineConstants)"/>
  </Target>

</Project>

然后在发布时指定我们想要的Platform

dotnet publish -r linux-arm64 -c Release /p:Platform=arm64 /p:PublishSingleFile=true /p:PublishTrimmed=true

这个命令的输出会返回这样的一行

TRACE;Is64Bit;RELEASE;NETCOREAPP;NETCOREAPP3_1

最后,在我们的代码中,我们可以根据程序编译的平台加载正确的非托管 DLL

#if Is64Bit
    [DllImport(@"Resources/HIDAPI/x64/hidapi")]
#else
    [DllImport(@"Resources/HIDAPI/x32/hidapi")]
#endif

【讨论】:

    猜你喜欢
    • 2020-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多