3

I'm writing a class that I wish to use on both Windows & Linux.

One of the methods that is in this class is accessing the Windows Registry

What I'm hoping to achieve is to somehow disable this particular method from being used when using a Linux machine.

First of all I did some research to see if there was something for .Net Core that would allow me to check which operating system is in use, I found this and sure enough that works.

When I implemented it into my code when accessing a method, I was hoping to disable the method that's accessing the windows registry, however the closest I could get to this was using a switch statement, something like this

switch (OS)
{
    case OSX:
    return;

    case LINUX:
    return
}

To return if the operating system was not supported, this worked, however I then thought disabling it from being accessed all together would be much better rather than throwing an error for an operating system thats not supported for that particular method

I then went on to look at preprocessor directives thinking that if I'm able to detect and disable parts of code depending the frameworks etc, maybe I could use something like this to disable parts of code depending on the operating system that way they could never be called even when trying to access the method

I went on from there to see if I could disable parts of code using preprocessor directives.
I found this.

I understand that it is for C++ however it seems to be the closest I could find for what I'm trying to achieve within .Net Core

In a perfect world, it would look something like this

    /// <summary>
    /// Get the file mime type
    /// </summary>
    /// <param name="filePathLocation">file path location</param>
    /// <returns></returns>
    `#if WINDOWS`
    public static string GetMimeType(this string filePathLocation)
    {
        if (filePathLocation.IsValidFilePath())
        {
            string mimeType = "application/unknown";
            string ext = Path.GetExtension(filePathLocation).ToLower();
            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);

            if (regKey != null && regKey.GetValue("Content Type") != null)
            {
                mimeType = regKey.GetValue("Content Type").ToString();
            }
            return mimeType;
        }
        return null;
    }
`#endif`

I did see #Define so I tried something like this #define IS_WINDOWS and added it to my class along with #if IS_WINDOWS however, I couldn't see how to change that value if I'm hoping to just reuse the static class over and over.

traveler3468
  • 1,557
  • 2
  • 15
  • 28
  • Could you explain in other what you don't like in the working method you've already found? I honestly don't understand why you don't want to use it. Also I don't understand why do you want to use pre-processor directives. These directives are defined and given to the compiler before the compilation starts. So maybe I'm wrong but it seems to be that for example you won't be able to compile your program on Windows and run it on Linux. In that case your `#if WINDOWS` would be true while it's actually not... – Jérôme MEVEL Sep 21 '18 at 01:48

2 Answers2

5

While you could pursue a route involving #define, it's compile-time and you'll loose a lot of .Net's multi-platform goodness. You'll also have to juggle multiple configurations, multiple builds, etc.

Where possible, hide the platform-dependent behavior behind a platform-independent abstraction and do the check at runtime using System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform:

interface IPlatform
{
    void DoSomething();
}

class WindowsImpl : IPlatform
{
    public void DoSomething()
    {
        // Do something on Windows
    }
}

class LinuxImpl : IPlatform
{
    public void DoSomething()
    {
        // Do something on Linux
    }
}

// Somewhere else
var platform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new WindowsImpl() : new LinuxImpl();
platform.DoSomething();

This works well for many things including PInvoke. You will be able to use the same binaries on either platform, and it will be easier to add OSX later.

If you need to isolate the platform-dependent code at compile-time (perhaps a package is Windows-only), MEF2/System.Composition can help you make a plugin framework where each platform gets its own assembly:

// In Windows.dll class library project
using System.Composition;

[Export(typeof(IPlatform))]
public class WindowsImpl : IPlatform
{
    public void DoSomething()
    {
        //...
    }
}

And then in your main program:

using System.Composition.Hosting;

var configuration = new ContainerConfiguration();
var asm = Assembly.LoadFrom(pathToWindowsDll);
configuration.WithAssembly(asm);
var host = configuration.CreateContainer();
var platform = host.GetExports<IPlatform>().FirstOrDefault();
Jake
  • 1,304
  • 9
  • 11
  • Hey Jake, thanks for your reply, I having a little trouble understanding your answer, I have a static class with a bunch of extension helpers for .net core. One of the extensions get the mime type of a file by accessing the registry which is quite impossible in linux and mac, I dont see how interfaces can help as I have just one class with the method there, is there something that I am missing? or was your idea how the interfaces with the two classes and repeat the code between the two? thanks again – traveler3468 Sep 14 '18 at 12:22
  • 1
    I'm saying you might want to consider using a runtime check to pick the right behavior rather than doing it at compile-time. Take your `GetMimeType()` function. Sure, the Windows implementation might use the registry, but there would likely also be a Linux implementation that uses something else. I don't have a Linux VM handy, but that code compiles on OSX. It fails at runtime with `System.Security.SecurityException` on `OpenSubKey()`, but the point is you don't need to `#ifdef` everywhere (including all the places where the function is called). – Jake Sep 14 '18 at 13:36
  • If you really want to "disable" it but not deal with all the places that call it, you can also use the [`ConditionalAttribute`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.conditionalattribute?view=netframework-4.7.2) – Jake Sep 14 '18 at 13:41
  • Thanks Jake, yeah im sure there is something for linux indeed, I may be able to implement something else, I will have to do some research, yeah I did see ConditionalAttribute however, before you compile, I think you need to do something like this `set OS_WINDOWS = 1` in order to only use the windows side, I was trying to make it so one dll would rule them all sorta thing, I may need to create different packages, I dont know at this stage, we will see I guess. – traveler3468 Sep 14 '18 at 13:42
1

I have a use-case where preprocessor directives are necessary. I found here that I can do this. As the site tells us, add the following to your project (.csproj) file:

<PropertyGroup>  
  <TargetFramework>...</TargetFramework>
  <OutputType>...</OutputType>  
  
  <!-- insert the following -->
  <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>

... then add these for each preprocessor option:

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

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

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

It is the individual <PropertyGroup Condition> child element that defines the constant so I can do this:

#if Linux
        private const string GLFW_LIB = "glfw";
#elif OSX
        private const string GLFW_LIB = "libglfw.3";
#elif Windows
        private const string GLFW_LIB = "glfw3";
#else
        // some error condition - unsupported platform
#endif
IAbstract
  • 19,551
  • 15
  • 98
  • 146