在 C# 和 .NET 中调用 P/调用时的功能检测

本文关键字:调用 功能 检测 NET | 更新日期: 2023-09-27 18:19:54

我正在尝试找到一种好方法来检测在 P/调用之前是否存在特征。例如调用本机StrCmpLogicalW函数:

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
   [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
   public static extern int StrCmpLogicalW(string psz1, string psz2);
}

将在某些没有此功能的系统上崩溃。

我不想执行版本检查,因为这是不好的做法,有时可能是错误的(例如,当功能向后移植时,或者当功能可以卸载时(。

正确的方法是检查是否存在从shlwapi.dll导出:

private static _StrCmpLogicalW: function(String psz1, String psz2): Integer;
private Boolean _StrCmpLogicalWInitialized;
public int StrCmpLogicalW(String psz1, psz2)
{
    if (!_StrCmpLogialInitialized)
    {
        _StrCmpLogicalW = GetProcedure("shlwapi.dll", "StrCmpLogicalW");
        _StrCmpLogicalWInitialized = true;
    }
    if (_StrCmpLogicalW)
       return _StrCmpLogicalW(psz1, psz2)
    else
       return String.Compare(psz1, psz2, StringComparison.CurrentCultureIgnoreCase);
}

当然,问题在于 C# 不支持函数指针,即:

_StrCmpLogicalW = GetProcedure("shlwapi.dll", "StrCmpLogicalW");

做不到。

所以我正在尝试寻找替代语法来在 .NET 中执行相同的逻辑。 到目前为止,我有以下伪代码,但我遇到了障碍:

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
   private Boolean IsSupported = false;
   private Boolean IsInitialized = false;
   [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, Export="StrCmpLogicalW", CaseSensitivie=false, SetsLastError=true, IsNative=false, SupportsPeanutMandMs=true)]
   private static extern int UnsafeStrCmpLogicalW(string psz1, string psz2);
   public int StrCmpLogicalW(string s1, string s2)
   {
       if (!IsInitialized) 
       {
          //todo: figure out how to loadLibrary in .net
          //todo: figure out how to getProcedureAddress in .net
          IsSupported = (result from getProcedureAddress is not null);
          IsInitialized = true;
       }
       if (IsSupported) 
          return UnsafeStrCmpLogicalW(s1, s2);
       else
          return String.Compare(s1, s2, StringComparison.CurrentCultureIgnoreCase);
   }
}

我需要一些帮助。


我想检测是否存在的一些导出的另一个示例是:

  • dwmapi.dll::DwmIsCompositionEnabled
  • dwmapi.dll::DwmExtendFrameIntoClientArea
  • dwmapi.dll::DwmGetColorizationColor
  • dwmapi.dll::DwmGetColorizationParameters(未记录的1,尚未按名称导出,序号 127(
  • dwmapi.dll::127(未记录的 1,DwmGetColorizationParameters(

1 自 Windows 7 SP1 起

.NET 中必须已经存在用于检查操作系统功能是否存在的设计模式。任何人都可以指出 .NET 中执行功能检测的首选方法的示例吗?

在 C# 和 .NET 中调用 P/调用时的功能检测

你可以P/Invoke到LoadLibraryW加载shlwapi.dll然后P/Invoke到GetProcAddressW来查找"StrCmpLogicalW"。如果返回 NULL,则它不存在。

您不需要 GetProcAddressW 中的实际返回值 - 只要它不是 NULL,您就知道您可以使用您选择的 P/Invoke 声明。

请注意,GetProcAddressW还支持按序号值导出的函数。

编辑:如果你想遵循某种模式,那么这可能会起作用:

首先定义一个帮助程序类NativeMethodResolver,告诉您库中是否存在方法:

public static class NativeMethodResolver
{
    public static bool MethodExists(string libraryName, string methodName)
    {
        var libraryPtr = LoadLibrary(libraryName);
        var procPtr = GetProcAddress(libraryPtr, methodName);
        return libraryPtr != UIntPtr.Zero && procPtr != UIntPtr.Zero;
    }
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern UIntPtr LoadLibrary(string lpFileName);
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    private static extern UIntPtr GetProcAddress(UIntPtr hModule, string lpProcName);
}

上面的帮助程序类可以由派生的SafeNativeMethod类使用,这些类有助于锅炉电镀一些常见的东西:

public abstract class SafeNativeMethod
{
    private readonly string libraryName;
    private readonly string methodName;
    private bool resolved;
    private bool exists;
    protected SafeNativeMethod(string libraryName, string methodName)
    {
        this.libraryName = libraryName;
        this.methodName = methodName;
    }
    protected bool CanInvoke
    {
        get
        {
            if (!this.resolved)
            {
                this.exists = Resolve();
                this.resolved = true;
            }
            return this.exists; 
        }            
    }
    private bool Resolve()
    {
        return NativeMethodResolver.MethodExists(this.libraryName, this.methodName);
    }
}

然后,定义自己的Invoke方法的派生类可以调用基CanInvoke,以查看是否应返回默认值(或默认实现(来代替所查找的本机方法的返回值。从你的问题中,我将以shlwapi.dll/StrCmpLogicalWdwmapi.dll/DwmIsCompositionEnabled作为SafeNativeMethod的示例实现:

public sealed class SafeStrCmpLogical : SafeNativeMethod
{
    public SafeStrCmpLogical()
        : base("shlwapi.dll", "StrCmpLogicalW")
    {           
    }
    public int Invoke(string psz1, string psz2)
    {
        return CanInvoke ? StrCmpLogicalW(psz1, psz2) : 0;
    }
    [DllImport("shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string psz1, string psz2);
}
public sealed class SafeDwmIsCompositionEnabled : SafeNativeMethod
{
    public SafeDwmIsCompositionEnabled()
        : base("dwmapi.dll", "DwmIsCompositionEnabled")
    {
    }
    public bool Invoke()
    {
        return CanInvoke ? DwmIsCompositionEnabled() : false;
    }
    [DllImport("dwmapi.dll", SetLastError = true, PreserveSig = false)]
    private static extern bool DwmIsCompositionEnabled();
}

然后可以像这样使用这两个:

static void Main()
{
    var StrCmpLogical = new SafeStrCmpLogical();
    var relation = StrCmpLogical.Invoke("first", "second");
    var DwmIsCompositionEnabled = new SafeDwmIsCompositionEnabled();
    var enabled = DwmIsCompositionEnabled.Invoke();
}
Marshal.Prelink可用于

获取 P/Invoke "未找到"异常,而无需实际调用该方法。以下是使用它来完成示例的方法:

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, Export="StrCmpLogicalW", CaseSensitivie=false, SetsLastError=true, IsNative=false, SupportsPeanutMandMs=true)]
    private static extern int UnsafeStrCmpLogicalW(string psz1, string psz2);
    static bool? _UnsafeStrCmpLogicalWIsSupported;
    static bool UnsafeStrCmpLogicalWIsSupported => _UnsafeStrCmpLogicalWIsSupported ?? IsSupported("UnsafeStrCmpLogicalW", out _UnsafeStrCmpLogicalWIsSupported);
    public static int StrCmpLogicalW(string s1, string s2)
    {
        if (UnsafeStrCmpLogicalWIsSupported)
            return UnsafeStrCmpLogicalW(s1, s2);
        else
            return String.Compare(s1, s2, StringComparison.CurrentCultureIgnoreCase);
    }
    static bool IsSupported(string method, out bool? field)
    {
        var result = IsSupported(method);
        field = result;
        return result;
    }
    static bool IsSupported(string method)
    {
        var methods = typeof(SafeNativeMethods).GetMethods().Where(m => m.Name == method).ToList();
        if (methods.Count == 0)
            throw new ArgumentException("Method not found: "+method, nameof(method));
        try
        {
            foreach (var m in methods)
                Marshal.Prelink(m);
            return true;
        }
        catch { return false; }
    }
}