Search code examples
c#.net-coremsbuild.net-standardpreprocessor-directive

Custom Is64BitOperatingSystem preprocessor directive


We can add custom preprocessor directives for Platform Conditional Compilation in .NET Core like this

<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>

I've tested, it's working fine.

Now I want to detect whether or not I'm on a 64 bits operating system. Here is my .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>

However when I run this code, my first if...else is working as expected but not my Is64BitOperatingSystem preprocessor directive

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

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

What am I doing wrong? I can't spot where's the mistake in my code.

Thank you

EDIT

To add more details about this, I included this code in a .NET Standard library that is called by a .NET Core project.

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

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

Before debugging, Visual Studio obviously compiles my application so at this stage checking the architecture using System.Environment.Is64BitOperatingSystem or a preprocessor directive should give the same results but it's not. I'm on a 64 bits machine and my preprocessor directive tells me I'm on a 32 bits architecture even if I change AnyCPU to x64 in Visual Studio Configuration Manager

Note that this answer is Windows specific and that one too because the solution is to call the SetDllDirectory function from kernel32.dll

But I want my code to be able to run on Linux.

EDIT 2:

For the sake of sharing a minimal sample here I actually removed the faulty part of my code.

It looks like this gives the expected result:

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

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

But this gives a faulty behavior:

<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>

If anybody could explain me that? I don't understand why adding the IsWindows condition is responsible for a different behavior on the Is64BitOperatingSystem preprocessor directive


Solution

  • I'd like to thank both JLRishe and Pavel Anikhouski for their answers. Although they were very useful, they were actually both not complete so I thought I should write myself another answer with all the necessary information.

    I created a sample project on Github so you can play with it. Here are the explanations

    Adding a Conditional Compilation Symbol in the Visual Studio Configuration Manager actually adds a DefineConstants node in the .csproj behind the hood.

    It doesn't need any new MsBuild variable declaration like I did in my question. It just uses an existing Platform variable which already exists.

    Moreover the way I used in my question seems incompatible with a dotnet publish command

    With a .csproj like this

    <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>
    

    Running the command

    dotnet publish -r win-x64 -c Release
    

    would return the following error

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

    We actually don't need to add an extra Is64BitOperatingSystem variable. All we need is to reuse the existing Platform variable like this

    <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>
    

    Then specify the Platform we want during the publish

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

    The output of this command would return a line like that

    TRACE;Is64Bit;RELEASE;NETCOREAPP;NETCOREAPP3_1

    Finally in our code we can load the correct unmanaged DLL depending of the platform the program has been compiled for

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