Search code examples
c#pinvokefeature-detection

Feature detection when P/Invoking in C# and .NET


i'm trying to find a good way to detect if a feature exists before P/Invoking. For example calling the native StrCmpLogicalW function:

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

will crash on some systems that do not have this feature.

i don't want to perform version checking, as that is bad practice, and can sometimes just be wrong (for example when functionality is back-ported, or when the functionality can be uninstalled).

The correct way, is to check for the presence of the export from 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);
}

The problem, of course, is that C# doesn't support function pointers, i.e.:

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

cannot be done.

So i'm trying to find alternative syntax to perform the same logic in .NET. i have the following pseudo-code so far, but i'm getting stymied:

[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);
   }
}

and i need some help.


Another example of some exports that i want to detect presence of would be:

  • dwmapi.dll::DwmIsCompositionEnabled
  • dwmapi.dll::DwmExtendFrameIntoClientArea
  • dwmapi.dll::DwmGetColorizationColor
  • dwmapi.dll::DwmGetColorizationParameters (undocumented1, not yet exported by name, ordinal 127)
  • dwmapi.dll::127 (undocumented1, DwmGetColorizationParameters)

1 as of Windows 7 SP1

There must already be a design pattern in .NET for checking the presence of OS features. Can anyone point me to an example of the preferred way in .NET to perform feature detection?


Solution

  • You could P/Invoke to LoadLibraryW to load shlwapi.dll and then P/Invoke to GetProcAddressW to find "StrCmpLogicalW". If NULL is returned, then it's not there.

    You don't need the actual returned value from GetProcAddressW - as long as it's not NULL, you know you can use the P/Invoke declaration of your choice.

    Note that GetProcAddressW also supports functions exported by ordinal value.

    EDIT: If you want to follow some kind of pattern, then this might work:

    First define a helper class NativeMethodResolver that tells you if a method exists in a library:

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

    The above helper class can be consumed by derived classes of SafeNativeMethod that aids in boiler plating some common stuff:

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

    A derived class that defines its own Invoke method can then call the base CanInvoke to see if a default value (or default implementation) should be returned in place of the return value of the sought native method. From your question, I'll take shlwapi.dll/StrCmpLogicalW and dwmapi.dll/DwmIsCompositionEnabled as example implementations for 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();
    }
    

    Those two can then be used like this:

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