Preprocessor Directive in C# for Importing Based on Platform

Preprocessor directive in C# for importing based on platform

Here's what you need to do.

First, go into Project-><project name> Properties... and go to the Build tab.

In there, in the textbox labelled "Conditional compilation symbols", add WIN32 for your x86 platform (selectable at the top of the dialog) and WIN64 for your x64 platform. Then save.

Note that if you have one for "AnyCPU", you probably want to remove that platform altogether, as it won't be safe.

Then, go into the source, and write this:

#if WIN64
[DllImport("ZLIB64.dll", CallingConvention=CallingConvention.Cdecl)]
#else
[DllImport("ZLIB32.dll", CallingConvention=CallingConvention.Cdecl)]
#endif

Note that when you view the source, one of the lines will look like it has been commented out, in that the entire line is in a gray font. This line is the one for the "other platform". If you select the platform in the toolbar, you'll notice that the syntax coloring follows suit.

Of course, after re-reading my answer I notice that you don't actually need to put WIN32 into the conditional symbols list as it isn't used, but it might be useful other places to do an #if on WIN32 instead of 64.

How to use #if to decide which platform is being compiled for in C#

You can add any constants you want to the .csproj file. These can be put into conditional property groups like the one below.

 <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<DefineConstants>TRACE;X64</DefineConstants>
...
</PropertyGroup>

For my Release x64 build, I have defined a X64 constant that I can use like this:

#if X64

#endif

Custom Is64BitOperatingSystem preprocessor directive

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

How to compile code (choose part of the code) according to the target platform?

There is no WINANY constant to determine OS platform your code runs on. Since .NET 3.1, the available common constants are WINDOWS, ANDROID, LINUX, IOS.

Your code above can be simplified to this:

        /// <summary>
/// Will log a Windows Event. Event are visible in the Application section with EventViewer.
/// </summary>
public static void LogWindowsEvent(LogType logType, string message, Exception ex = null)
{
#if WINDOWS
string appName = AppInfo.AppName;
if (!EventLog.SourceExists(appName))
{
EventLog.CreateEventSource(appName, "Application");

if (logType == LogType.LogException)
{
EventLog.WriteEntry(appName, $"Exception happen: {ex} at StackTrace: {ex.StackTrace}.", EventLogEntryType.Error);
}
else
{
EventLogEntryType eventLogEntryType = EventLogEntryType.Error;
switch (logType)
{
case LogType.LogWarning:
eventLogEntryType = EventLogEntryType.Warning;
break;
case LogType.LogMessage:
eventLogEntryType = EventLogEntryType.Information;
break;
}

EventLog.WriteEntry(appName, message, eventLogEntryType);
}
}
#else
// Empty code for non Windows
#endif
}

For more information, see also this official document on .NET Core design document:

https://github.com/dotnet/designs/blob/main/accepted/2020/net5/net5.md#scenarios-and-user-experience

Preprocessor directives across different files in C#

In your .csproj there is a section:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>TRACE;DEBUG;LINQ</DefineConstants>
</PropertyGroup>
</Project>

If you want extra preprocessors you can add them there.

Or via the properties of the project which will add them there automatically for you. In properties under the Build tab.

Running NUnit test depending on a condition

I don't know if there's a built-in mechanism in NUnit to handle this scenario, but at the very least you can use preprocessor directives.

For instance, create a "Debug x86" solution configuration, targeting x86. Then define the DEBUG_X86 conditional compilation symbol (in the properties of the project). Finally, surround your unit test with preprocessor directives:

#if DEBUG_X86
[Test]
public void Test()
{
// This test will only run when compiled with Debug x86
}
#endif

Edit: Actually, you don't even have to create a new solution configuration, as it's possible to define the conditional symbols depending on the platform (https://stackoverflow.com/a/1313450/869621). So define a WIN32 compilation symbol, and surround your test with it:

#if WIN32
[Test]
public void Test()
{
// This test will only run when compiled for x86
}
#endif


Related Topics



Leave a reply



Submit