2012-01-09 9 views
13

Sto cercando di trovare un buon modo per rilevare se una funzione esiste prima di P/Invocazione. Per esempio chiamando il nativo StrCmpLogicalW funzione di:Rilevamento di funzioni durante P/Invocazione in C# e .NET

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

sarà in crash su alcuni sistemi che non dispongono di questa funzionalità.

i don't want to perform version checking, come quello è cattiva pratica, ed a volte può essere solo sbagliato (per esempio quando la funzionalità è di back-porting, o quando la funzionalità può essere disinstallato).

Il modo corretto, è quello di verificare la presenza delle esportazioni da 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); 
} 

Il problema, naturalmente, è che C# non supporta i puntatori a funzione, vale a dire:

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

non può essere eseguito.

Quindi sto cercando di trovare la sintassi alternativa per eseguire la stessa logica in .NET. Ho il seguente pseudo-codice finora, ma sto ottenendo ostacolato:

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

e ho bisogno di aiuto.


Un altro esempio di alcune esportazioni che voglio rilevare la presenza di sarebbe:

  • dwmapi.dll::DwmIsCompositionEnabled
  • dwmapi.dll::DwmExtendFrameIntoClientArea
  • dwmapi.dll::DwmGetColorizationColor
  • dwmapi.dll::DwmGetColorizationParameters (non documentato , non ancora esportati per nome, ordinale 127)
  • dwmapi.dll::127 (senza documenti , DwmGetColorizationParameters)

come di Windows 7 SP1

Ci deve essere già un modello di progettazione in .NET per verificare la presenza di caratteristiche del sistema operativo. Qualcuno può indicarmi un esempio del modo preferito in .NET per eseguire il rilevamento delle funzionalità?

+0

Lo schema di progettazione nel codice sorgente di .NET Framework è per controllare i numeri di versione del sistema operativo, ma per farlo * in modo intelligente * come conclude Larry Osterman nel suo post sul blog. Sono d'accordo che la soluzione di Johann sia probabilmente migliore, ma sono anche un ragazzo Win32. 'LoadLibrary' e' GetProcAddress' solo * hanno senso * per me. Trascorro la maggior parte del mio tempo scrivendo le definizioni P/Invoke quando scrivo codice .NET. Non sono sicuro che sia effettivamente una buona cosa. –

+0

@Cody: * Non sono sicuro che sia effettivamente una buona cosa * - probabilmente no, no. :-) –

+0

@CodeGray Non si può fare affidamento sui numeri di versione. Una funzionalità potrebbe essere stata trasferita retroattivamente su un sistema operativo (rendendo errati i numeri di versione). Una funzione potrebbe anche non essere installata dall'utente (rendendo errati i numeri di versione). –

risposta

6

È possibile P/Richiamare a LoadLibraryW per caricare shlwapi.dll e quindi P/Invoke per GetProcAddressW per trovare "StrCmpLogicalW". Se viene restituito NULL, non è lì.

Non è necessario il valore restituito effettivo da GetProcAddressW - finché non è NULL, è possibile utilizzare la dichiarazione P/Invoke di propria scelta.

Nota che GetProcAddressW supporta anche le funzioni esportate dal valore ordinale.

EDIT: Se si vuole seguire un qualche tipo di modello, allora questo potrebbe funzionare:

prima definire una classe di supporto NativeMethodResolver che ti dice se un metodo esiste in una libreria:

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

La classe di supporto di cui sopra può essere consumato da classi derivate di SafeNativeMethod che aiuta nella caldaia placcatura alcune cose comuni:

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

Una classe derivata che definisce il proprio metodo Invoke può quindi chiamare la base CanInvoke per verificare se un valore predefinito (o un'implementazione predefinita) deve essere restituito al posto del valore restituito del metodo nativo ricercato. Dalla tua domanda, mi prendo shlwapi.dll/StrCmpLogicalW e dwmapi.dll/DwmIsCompositionEnabled come esempio implementazioni per 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(); 
} 

Quei due può quindi essere utilizzato in questo modo:

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

    var DwmIsCompositionEnabled = new SafeDwmIsCompositionEnabled(); 
    var enabled = DwmIsCompositionEnabled.Invoke(); 
} 
+3

È anche possibile utilizzare Marshal.GetDelegateForFunctionPointer() per convertire l'indirizzo restituito a un delegato. – Hans

+0

@Hans: Sì, a meno che non si utilizzi .NET Compact Framework. Quel metodo non è supportato nella classe 'Marshal' lì. –

+0

Vedere pinvoke.net per la sintassi di LoadLibrary, GetProcAddress e FreeLibrary. – dgvid