预处理器:获取操作系统的 .Net Core。

4

我正在编写一个类,希望可以在Windows和Linux上使用。

其中一个方法涉及访问Windows注册表

我希望实现的目标是,在Linux机器上使用时禁用这个特定的方法。

首先,我进行了一些研究,看看是否有.Net Core可以检查使用的操作系统的内容,我找到了this,它确实有效。

当我将其实施到我的代码中,访问某个方法时,我希望禁用访问Windows注册表的方法,但是最接近的方法是使用switch语句,就像这样

switch (OS)
{
    case OSX:
    return;

    case LINUX:
    return
}

如果操作系统不受支持,则使用return。这个方法可行,但我认为禁止访问比为不支持该特定方法的操作系统抛出错误要好得多。

接着,我研究了预处理指令,认为如果能够检测和禁用代码中的某些部分,依赖于框架等等,也许我可以使用类似的东西来禁用代码中的某些部分,这样即使在尝试访问该方法时,它们也永远无法被调用。

然后我继续探索如何使用预处理指令禁用代码的某些部分。
我发现了this

我知道这是针对C++的,但它似乎是我在.Net Core中找到的最接近我想要实现的内容。

在完美的世界里,它会看起来像这样

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

我看到了#Define,因此尝试了类似于这样的内容:#define IS_WINDOWS,并将其与#if IS_WINDOWS一起添加到我的类中, 但是,如果我希望只重复使用静态类,我不知道如何更改该值。


你能否用其他方式解释一下你不喜欢已经找到的工作方法的原因?我真的不明白为什么你不想使用它。而且我也不明白为什么你想要使用预处理器指令。这些指令在编译开始之前就被定义并提供给编译器。所以也许我错了,但似乎例如你将无法在Windows上编译程序并在Linux上运行它。在这种情况下,你的#if WINDOWS会是true,而实际上并不是... - Jérôme MEVEL
2个回答

5

虽然你可以选择使用#define的方法,但这种方法是在编译时进行的,你会失去很多.Net跨平台的优点。你还需要处理多个配置、多个构建等问题。

如果有可能,尽量把依赖于特定平台的行为隐藏在一个与平台无关的抽象层后,在运行时使用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();

这对许多事情都很有效,包括PInvoke。您将能够在任何平台上使用相同的二进制文件,并且稍后添加OSX将更容易。
如果您需要在编译时隔离平台相关代码(可能是Windows专用包),MEF2/System.Composition可以帮助您创建插件框架,其中每个平台都有自己的程序集:
// In Windows.dll class library project
using System.Composition;

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

然后在你的主程序中:

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();

嘿,杰克,感谢你的回复。我有一点难以理解你的答案。我有一个静态类,其中包含了许多用于 .net core 的扩展帮助程序。其中一个扩展可以通过访问注册表来获取文件的 MIME 类型,在 Linux 和 Mac 上几乎不可能实现。我不明白接口如何帮助我,因为我只有一个类和一个方法。我是否遗漏了什么?或者你的想法是使用两个类和接口来重复代码?再次感谢。 - traveler3468
1
我想说的是,你可能想考虑使用运行时检查来选择正确的行为,而不是在编译时进行。 以你的 GetMimeType() 函数为例。当然,Windows 实现可能会使用注册表,但很可能也会有一个使用其他东西的 Linux 实现。 我手头没有 Linux 虚拟机,但那段代码可以在 OSX 上编译。它在运行时失败,出现 System.Security.SecurityExceptionOpenSubKey() 上,但重点是你不需要到处使用 #ifdef(包括调用该函数的所有地方)。 - Jake
如果你真的想要“禁用”它,但不想处理所有调用它的地方,你也可以使用 ConditionalAttribute - Jake
谢谢Jake,是的,我确信Linux上有类似的东西,也许我可以实现其他的东西,我需要做一些研究。在编译之前,我看到了ConditionalAttribute,但我认为你需要像这样做set OS_WINDOWS = 1,以便只使用Windows端,我试图让一个dll控制所有的事情,可能需要创建不同的包,在这个阶段我还不知道,我们拭目以待吧。 - traveler3468

3

我有一个需要使用预处理指令的用例。我在这里找到了方法。如该网站所述,在项目 (.csproj) 文件中添加以下内容:

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

然后为每个预处理器选项添加以下内容:

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

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

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

这是一个个体的<PropertyGroup Condition>子元素,它定义了常量,因此我可以这样做:

#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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接