题 P /调用C#和.NET时的特征检测


我正试图在P / Invoking之前找到一种检测功能是否存在的好方法。例如调用本机 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中执行特征检测的首选方法的示例吗?


13
2018-01-09 20:47


起源


.NET Framework源代码中的设计模式是检查操作系统版本号,但这样做 智能 拉里奥斯特曼最终在他的博客文章中得出结论。我同意Johann的解决方案可能更好,但我也是Win32的人。 LoadLibrary 和 GetProcAddress 只是 合理 对我来说。在编写.NET代码时,我花了大部分时间编写P / Invoke定义。我不确定这实际上是不是一件好事。 - Cody Gray♦
@Cody: 我不确定这实际上是不是一件好事  - 可能不是,不。 :-) - Johann Gerell
@CodeGray您不能依赖版本号。功能可能已追溯移植到操作系统(使版本号错误)。用户也可能未安装功能(使版本号错误)。 - Ian Boyd
是的你可以。它只需要你做你的研究。你必须做足够的研究,找出你想要调用的API的位置(即,什么DLL),即使你使用Johann的解决方案,这与确定它是否可以是没有多大区别可选安装或者如果它已追溯移植到旧操作系统。旧的操作系统是已知的数量,因为它们在您的代码编写时已经被释放,因此您可以自己检查和验证所有这些。您特别询问了.NET中的设计模式,这就是它的作用。 - Cody Gray♦


答案:


你可以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 / StrCmpLogicalW 和 dwmapi.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();
}

6
2018-01-09 20:53



您还可以使用Marshal.GetDelegateForFunctionPointer()将返回的地址转换为委托。 - Hans
@Hans:是的,除非您使用的是.NET Compact Framework。该方法不受支持 Marshal 在那里上课。 - Johann Gerell
有关LoadLibrary,GetProcAddress和FreeLibrary的语法,请参见pinvoke.net。 - dgvid
我正试图想出设计模式。我使用了多少个标志(可用,检查过),在哪里执行此测试(在静态初始化期间?首次使用时?使用在构造期间执行检查的类并使类成为单例?使用静态方法调用内部singleton?我分配委托方法并检查方法变量是否正确 null? 能够 一个方法委托正在 null?等等,我正在寻找设计模式。 - Ian Boyd
@Ian:请看我的 编辑。 - Johann Gerell