【问题标题】:Feature detection when P/Invoking in C# and .NET在 C# 和 .NET 中进行 P/Invoking 时的特征检测
【发布时间】:2012-02-06 09:01:02
【问题描述】:

我正在尝试找到一种在 P/Invoking 之前检测功能是否存在的好方法。比如调用原生的StrCmpLogicalW函数:

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

会在一些没有此功能的系统上崩溃。

i don't want to perform version checking,因为这是不好的做法,而且有时可能是错误的(例如,当功能被向后移植时,或者当功能可以被卸载时)。

正确的方法,是检查从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 中执行特征检测的首选方式的示例?

【问题讨论】:

  • .NET Framework 源代码中的设计模式是检查操作系统版本号,但这样做智能正如 Larry Osterman 最终在他的博客文章中总结的那样。我同意 Johann 的解决方案可能更好,但我也是 Win32 人。 LoadLibraryGetProcAddress 对我来说只是有意义。在编写 .NET 代码时,我大部分时间都在编写 P/Invoke 定义。我不确定这是否真的是一件好事。
  • @Cody:我不确定这是否真的是一件好事 - 可能不是,不是。 :-)
  • @CodeGray 你不能依赖版本号。一项功能可能已被追溯移植到操作系统(使版本号错误)。用户也可能没有安装某个功能(使版本号错误)。
  • 是的,你可以。它只需要你做你的研究。即使您使用 Johann 的解决方案,您也必须进行足够多的研究才能确定在哪里可以找到您希望调用的 API(即,什么 DLL),这与弄清楚它是否可以是没有太大区别可选安装,或者如果它已被追溯移植到旧操作系统。旧操作系统是已知数量,因为它们在编写代码时已经发布,因此您可以自己检查和验证所有这些。您专门询问了 .NET 中的设计模式,这就是它的作用。

标签: c# pinvoke feature-detection


【解决方案1】:

您可以 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.GetDelegateForFunctionPointer() 将返回的地址转换为委托。
  • @Hans:是的,除非您使用的是 .NET Compact Framework。那里的Marshal 类不支持该方法。
  • 有关 LoadLibrary、GetProcAddress 和 FreeLibrary 的语法,请参见 pinvoke.net。
  • 我正在尝试提出设计模式。我使用多少个标志(可用,已检查),在哪里执行此测试(在静态初始化期间?首次使用时?使用在构造期间执行检查的类并使该类成为单例?有调用内部的静态方法单例?我是否分配委托方法并检查方法变量是否为null可以方法委托为null?等,等等。我正在寻找设计模式。
  • @Ian:请看我的编辑
猜你喜欢
  • 2013-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多