2

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

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
Jérôme MEVEL
  • 7,031
  • 6
  • 46
  • 78
  • 1
    Please explain what you mean when you say it's not working. What are you actually doing to try this out? `if (System.Environment.Is64BitOperatingSystem)` and `#if Is64BitOperatingSystem` mean very different things here. The first would depend on where the code is running. The second would depend on where the code is _compiled_. – JLRishe Dec 14 '19 at 11:50
  • So that is not actually your .csproj file, what you posted is corrupt. – Hans Passant Dec 14 '19 at 12:14
  • @HansPassant what I posted is not corrupt, the first code block is a quote from the blog post I linked because it's a good practice on SO. I wrote `Here is my .csproj` for the 2nd code block – Jérôme MEVEL Dec 14 '19 at 12:37
  • @JérômeMEVEL You have excess closing `` tag in your `csproj` file. Also I can't reproduce your issue, the `Is64BitOperatingSystem` works as expected – Pavel Anikhouski Dec 14 '19 at 12:43
  • @PavelAnikhouski Oops that was just a copy paste issue. Also now both my .NET Standard library and my .NET Core project have `x64`, I recompiled my code but I still get the issue – Jérôme MEVEL Dec 14 '19 at 12:52
  • If you downvote, please explain why. I think my question is well enough written with some code samples – Jérôme MEVEL Dec 14 '19 at 13:30
  • @JérômeMEVEL I'm not downvoting, but your issue isn't reproducible – Pavel Anikhouski Dec 14 '19 at 13:39
  • @PavelAnikhouski sorry, this comment wasn't specifically for you but for whoever is downvoting. I don't know what else I can provide to reproduce this issue. My .NET Core project is an almost empty console application at this point. Nothing special in its `.csproj` – Jérôme MEVEL Dec 14 '19 at 13:48
  • @PavelAnikhouski I edited my question. I actually found the source of the faulty behavior but I really couldn't tell why. It doesn't make sense to me – Jérôme MEVEL Dec 14 '19 at 14:49
  • Why are you trying to go about things this way rather than just having different build configurations in your .sln/.csproj? As I said, this would make it so that the compilation result would be dependent on the the bitness of the operating system where the code is built, meaning you'd have to have separate build machines for the 32-bit and 64-bit builds. If you used separate build configurations, you could build both versions on the same machine. – JLRishe Dec 14 '19 at 15:18
  • @JérômeMEVEL Please, have a look at my answer. The reason of the faulty behavior is the way of setting the `DefineConstants`. You'll need to set them only once using semicolon separated list of values – Pavel Anikhouski Dec 14 '19 at 22:06

3 Answers3

3

The reason why it isn't working in last edit is that conditional constants in DefineConstants can't be defined separately, the value of this property is semicolon separated list of values and should be defined by adding an new constant to existing list (many thanks to @Orace for the help)

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

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

You can pass these values to msbuild command line as well.

The following code will work as expected

#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");

It will display:

built in Windows! 
built on x64
running on x64

You can add a Linux constant on the same way as well.

The following msbuild target will help you to check which constants are defined in a project

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

In my test app it's showing TRACE;Windows;Is64BitOperatingSystem;DEBUG;NETCOREAPP;NETCOREAPP2_1

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
2

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 this is your requirement, then I think you are going about things the wrong way. The approach you are trying to use will make the library dependent on the architecture where it was built and nothing else.

Compilation directives, as the name implies, take effect at compile time. They have no effect at runtime. If you want to have different [DllImport]s for different architectures, you will need different builds. And typically the way to go about that is to have different build configurations.

In Configuration Manager, create two separate solution platforms, with corresponding project platforms:

Configuration Manager Build Configurations

In your Project properties -> Build tab, specify a conditional compilation directive for one of the two:

Project Properties

In your code, use the compilation symbol:

#if Is64Bit
    [DllImport(@"Resources/HIDAPI/x64/hidapi")]
#else
    [DllImport(@"Resources/HIDAPI/x32/hidapi")]
#endif
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • I must say even after reading your answer several times I'm still confused about it. `The approach you are trying to use will make the library dependent on the architecture where it was built and nothing else` but then you suggest me to have several `build configurations` instead... Are you saying with my way, even if I try compiling for the 32 bits architecture it would still consider `Is64BitOperatingSystem` as `true` because I compiled it on a 64 bits machine? Could you please explain a bit more the difference between a `DefineConstants` and a `Conditional compilation symbol`? Thanks – Jérôme MEVEL Dec 16 '19 at 08:34
  • @JérômeMEVEL _"Are you saying with my way, even if I try compiling for the 32 bits architecture it would still consider Is64BitOperatingSystem as true because I compiled it on a 64 bits machine?"_ Yes, AFAIK `Environment.Is64BitOperatingSystem` returns the bitness of the actual OS, not the bitness of the assembly. Feel free to try that out for yourself and verify, but that is what I found. So in order to use the approach you're trying, you would have to actually _build_ your program on a 32-bit OS in order to do your 32-bit build. With the above, you can build both on one machine. – JLRishe Dec 16 '19 at 10:14
  • @JérômeMEVEL _"Could you please explain a bit more the difference between a DefineConstants and a Conditional compilation symbol?"_ They are essentially the same thing. "Conditional compilation symbol" is just the name given to them in the project properties pane. – JLRishe Dec 16 '19 at 10:15
  • thanks for your answer, this helped me a lot like [Pavel answer's](https://stackoverflow.com/a/59339550/1203116). I upvoted but your explanation using the Visual Studio interface wasn't clear enough IMO. I wrote my own answer to clarify everything with some `.csproj` examples – Jérôme MEVEL Dec 22 '19 at 15:01
1

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
Jérôme MEVEL
  • 7,031
  • 6
  • 46
  • 78