Recherche…


Remarques

Travailler avec l'API Win32 en utilisant C #

Windows expose de nombreuses fonctionnalités sous la forme d'une API Win32. En utilisant ces API, vous pouvez effectuer des opérations directes dans Windows, ce qui améliore les performances de votre application.Source Cliquez ici

Windows expose une large gamme d'API. Pour obtenir des informations sur les différentes API, vous pouvez consulter des sites tels que pinvoke .

Fonction d'importation à partir d'une DLL C ++ non gérée

Voici un exemple d'importation d'une fonction définie dans une DLL C ++ non gérée. Dans le code source C ++ pour "myDLL.dll", la fonction add est définie:

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
    return a + b;
}

Ensuite, il peut être inclus dans un programme C # comme suit:

class Program
{
    // This line will import the C++ method.
    // The name specified in the DllImport attribute must be the DLL name.
    // The names of parameters are unimportant, but the types must be correct.
    [DllImport("myDLL.dll")]
    private static extern int add(int left, int right);

    static void Main(string[] args)
    {
        //The extern method can be called just as any other C# method.
        Console.WriteLine(add(1, 2));
    }
}

Reportez-vous à la section Conventions d'appel et nom C ++ pour savoir pourquoi extern "C" et __stdcall sont nécessaires.

Trouver la bibliothèque dynamique

Lorsque la méthode extern est invoquée pour la première fois, le programme C # recherche et charge la DLL appropriée. Pour plus d'informations sur l'emplacement de recherche de la DLL et sur la manière dont vous pouvez influencer les emplacements de recherche, consultez cette question sur le stackoverflow .

Code simple pour exposer la classe pour com

using System;
using System.Runtime.InteropServices;
 
namespace ComLibrary
{
    [ComVisible(true)]
    public interface IMainType
    {
        int GetInt();
 
        void StartTime();
 
        int StopTime();
    }
 
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class MainType : IMainType
    {
        private Stopwatch stopWatch;
 
        public int GetInt()
        {
            return 0;
        }
 
        public void StartTime()
        {
            stopWatch= new Stopwatch();
            stopWatch.Start();
        }
 
        public int StopTime()
        {
            return (int)stopWatch.ElapsedMilliseconds;
        }
    }
}

C ++ nom mangling

Les compilateurs C ++ codent des informations supplémentaires dans les noms des fonctions exportées, telles que les types d'argument, pour créer des surcharges avec différents arguments possibles. Ce processus s'appelle le mangling name . Cela pose des problèmes avec l'importation de fonctions dans C # (et l'interopérabilité avec d'autres langages en général), le nom de la fonction int add(int a, int b) n'étant plus add , cela peut être ?add@@YAHHH@Z , _add@8 ou autre chose, en fonction du compilateur et de la convention d'appel.

Il y a plusieurs façons de résoudre le problème de la manipulation de noms:

  • Exporter des fonctions en utilisant extern "C" pour basculer vers la liaison externe C qui utilise la gestion du nom C:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    Le nom de la fonction sera toujours _add@8 ( _add@8 ), mais le nom de StdCall + extern "C" est reconnu par le compilateur C #.

  • Spécification des noms de fonction exportés dans le fichier de définition de module myDLL.def :

    EXPORTS
      add
    
    int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    Le nom de la fonction sera pur add dans ce cas.

  • Importer un nom mutilé. Vous aurez besoin d'un visualiseur de DLL pour voir le nom tronqué, vous pourrez alors le spécifier explicitement:

    __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
    

Conventions d'appel

Il existe plusieurs conventions d'appel de fonctions, spécifiant qui (appelant ou appelé) affiche les arguments de la pile, comment les arguments sont transmis et dans quel ordre. C ++ utilise la convention d'appel Cdecl par défaut, mais C # attend StdCall , qui est généralement utilisé par l'API Windows. Vous devez changer l'un ou l'autre:

  • Changer la convention d' StdCall en StdCall en C ++:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    
  • Ou, changez la convention d' Cdecl en Cdecl en C #:

    extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
    
    [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    

Si vous souhaitez utiliser une fonction avec la convention d'appel Cdecl et un nom tronqué, votre code ressemblera à ceci:

__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
           EntryPoint = "?add@@YAHHH@Z")]
  • thiscall ( __thiscall ) est principalement utilisé dans les fonctions membres d'une classe.

  • Lorsqu'une fonction utilise thiscall ( __thiscall ), un pointeur vers la classe est transmis comme premier paramètre.

Chargement et déchargement dynamiques de DLL non gérées

Lorsque vous utilisez l'attribut DllImport vous devez connaître le nom de la DLL et de la méthode au moment de la compilation . Si vous souhaitez être plus flexible et décider au moment de l' exécution quelles DLL et méthodes charger, vous pouvez utiliser les méthodes de l'API Windows LoadLibrary() , GetProcAddress() et FreeLibrary() . Cela peut être utile si la bibliothèque à utiliser dépend des conditions d'exécution.

Le pointeur renvoyé par GetProcAddress() peut être converti en un délégué à l'aide de Marshal.GetDelegateForFunctionPointer() .

L'exemple de code suivant illustre cela avec le myDLL.dll des exemples précédents:

class Program
{
    // import necessary API as shown in other examples
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr LoadLibrary(string lib);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void FreeLibrary(IntPtr module);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr module, string proc);

    // declare a delegate with the required signature
    private delegate int AddDelegate(int a, int b);

    private static void Main()
    {
        // load the dll
        IntPtr module = LoadLibrary("myDLL.dll");
        if (module == IntPtr.Zero) // error handling
        {
            Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}");
            return;
        }

        // get a "pointer" to the method
        IntPtr method = GetProcAddress(module, "add");
        if (method == IntPtr.Zero) // error handling
        {
            Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}");
            FreeLibrary(module);  // unload library
            return;
        }
            
        // convert "pointer" to delegate
        AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));
    
        // use function    
        int result = add(750, 300);
        
        // unload library   
        FreeLibrary(module);
    }
}

Traitement des erreurs Win32

Lorsque vous utilisez des méthodes d'interopérabilité, vous pouvez utiliser l'API GetLastError pour obtenir des informations supplémentaires sur vos appels d'API.

Attribut DllImport Attribut SetLastError

SetLastError = true

Indique que l'appelé appellera SetLastError (fonction API Win32).

SetLastError = false

Indique que l'appelé n'appellera pas SetLastError (fonction API Win32), vous ne recevrez donc pas d'informations d'erreur.

  • Lorsque SetLastError n'est pas défini, il est défini sur false (valeur par défaut).

  • Vous pouvez obtenir le code d'erreur à l'aide de la méthode Marshal.GetLastWin32Error:

Exemple:

[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);

Si vous essayez d'ouvrir mutex qui n'existe pas, GetLastError retournera ERROR_FILE_NOT_FOUND .

var lastErrorCode = Marshal.GetLastWin32Error();

if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
    //Deal with error         
}

Les codes d'erreur du système peuvent être trouvés ici:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx

API GetLastError

Il existe une API GetLastError native que vous pouvez également utiliser:

[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
  • Lorsque vous appelez l'API Win32 à partir du code managé, vous devez toujours utiliser l' erreur Marshal.GetLastWin32Error .

Voici pourquoi:

Entre votre appel Win32 qui définit l'erreur (appelle SetLastError), le CLR peut également appeler d'autres appels Win32 pouvant appeler SetLastError , ce comportement peut remplacer votre valeur d'erreur. Dans ce scénario, si vous appelez GetLastError, vous pouvez obtenir une erreur non valide.

Si vous définissez SetLastError = true , le CLR récupère le code d'erreur avant d'exécuter d'autres appels Win32.

Objet épinglé

GC (Garbage Collector) est responsable du nettoyage de nos déchets.

Alors que GC nettoie nos déchets, il supprime les objets inutilisés du tas géré, ce qui provoque une fragmentation du tas. Lorsque le GC est terminé avec la suppression, il effectue une compression de tas (défragmintation) qui consiste à déplacer des objets sur le tas.

Comme GC n'est pas déterministe, lors du passage d'une référence / d'un pointeur d'objet géré à un code natif, GC peut intervenir à tout moment, s'il survient juste après l'appel Inerop. être déplacé sur le tas géré - par conséquent, nous obtenons une référence non valide du côté géré.

Dans ce scénario, vous devez épingler l'objet avant de le transmettre au code natif.

Objet épinglé

L'objet épinglé est un objet qui n'est pas autorisé à se déplacer par GC.

Poignée épinglée Gc

Vous pouvez créer un objet pin en utilisant la méthode Gc.Alloc

GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned); 
  • L'obtention d'un objet GCHandle épinglé à un objet géré marque un objet spécifique comme un objet qui ne peut pas être déplacé par GC , jusqu'à ce que la poignée soit libérée.

Exemple:

[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
       
public void EnterCriticalSection(CRITICAL_SECTION section)
{
    try
    {
        GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned); 
        EnterCriticalSection(handle.AddrOfPinnedObject());
        //Do Some Critical Work
        LeaveCriticalSection(handle.AddrOfPinnedObject());
    }
    finaly
    {
        handle.Free()
    }
}

Précautions

  • Lorsque vous épinglez (surtout les plus gros), l'objet essaye de libérer le GcHandle épinglé aussi rapidement que possible, car il interrompt la défragmentation du tas.
  • Si vous oubliez de libérer GcHandle, rien ne se passera. Faites-le dans une section de code sécurisée (telle que finaly)

Structures de lecture avec le maréchal

La classe Marshal contient une fonction nommée PtrToStructure , cette fonction nous donne la possibilité de lire des structures par un pointeur non géré.

La fonction PtrToStructure a beaucoup de surcharges, mais toutes ont la même intention.

PtrToStructure générique:

public static T PtrToStructure<T>(IntPtr ptr);

T -type de structure.

ptr - Un pointeur sur un bloc de mémoire non géré.

Exemple:

NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);       
  • Si vous traitez des objets gérés en lisant des structures natives, n'oubliez pas d'épingler votre objet :)
 T Read<T>(byte[] buffer)
    {
        T result = default(T);
        
        var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    
        try
        {
            result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());
        }
        finally
        {
            gch.Free();
        }
        
        return result;
    }


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow