自定义 Is64BitOperatingSystem 预处理器指令

Custom Is64BitOperatingSystem preprocessor directive

我们可以像这样为 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 is Windows specific and 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 预处理器指令

的不同行为

I want my library to detect the current architecture it's running on (or has been compiled for) so I can do something like this

如果这是你的要求,那么我认为你做事的方式是错误的。您尝试使用的方法将使库依赖于构建它的架构,而不是其他任何东西。

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

在 Configuration Manager 中,创建两个独立的解决方案平台,以及相应的项目平台:

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

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

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

它在上次编辑中不起作用的原因是 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

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

我创建了一个 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

会return下面的错误

DetectArchitectureSample.csproj(7,29): error MSB4185: The function "Is64BitOperatingSystem" on type "System.Environment" is not available for execution as an MSBuild property function.

我们实际上不需要添加额外的 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

这个命令的输出会return这样一行

TRACE;Is64Bit;RELEASE;NETCOREAPP;NETCOREAPP3_1

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

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