Подтвердить что ты не робот

Обнаружение функции, когда P/Invoking в С# и .NET

Я пытаюсь найти хороший способ определить, существует ли функция до P/Invoking. Например, вызов функции native 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);
}

Проблема, конечно, в том, что С# не поддерживает указатели на функции, то есть:

_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 для обнаружения функций?

4b9b3361

Ответ 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/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();
}