Utiliser une DLL C / C++ sous Windows avec C#

Avec C# nous utilisons généralement du code managé, plus sûr et plus conforme à la méthodologie des nouvelles versions de Windows. Cependant, au vu du passé programmatique des acteurs Microsoft, il serait dommage d'attendre que les anciennes bibliothèques soient transformées pour passer de format de DLL à du code managé.

Pour pouvoir utiliser les DLL (écrites en C ou C++), nous devons utiliser des directives de compilation DllImport et déclarer les fonctions exportées à utiliser en tant que static extern.


[DllImport("nom_de_la_dll" [, options]]
protected static extern type_de_la_fonction nom_de_la_fonction(arguments)
Ces déclarations doivent être faites depuis l'intérieur de la classe appelante car en code managé il n'est pas permis de procéder à des déclarations hors d'une classe. Ensuite il ne reste plus qu'à utiliser la fonction comme n'importe quelle méthode classique.

Parmi les différentes options, vous pourrez être amenés à forcer la convention d'appel CDecl à la place de StdCall, cette dernière étant la convention d'appel standard de DllImport

[DllImport("nom_dll", CallingConvention=CallingConvention.CDecl)]
Si jamais vous obtenez une erreur du genre "A call to PInvoke function has unbalanced the stack...", supprimez la convention d'appel CDecl (ou bien ajoutez-la). Cette convention fixe qui doit nettoyer la pile d'appel après le retour des valeurs de la fonction externe. CDecl définit que c'est l'appelant qui doit nettoyer la pile, alors que StdCall assume que c'est la fonction appelée qui doit nettoyer la pile d'appel de fonctions.

D'autres options sont disponibles mais l'intérêt reste faible : ExactSpelling, EntryPoint... Je vous laisse consulter une documentation plus précise pour ces points de détail...

Exemple complet de DllImport

Pour notre exemple, nous devons préalablement créer une fenêtre et y insérer un simple ListBox que nous appelons bêtement lst. Les explications sont dans le code... Une chose est tout de même à mettre en évidence : nous allons devoir mettre à jour un contrôle Windows depuis une méthode déclarée static. Ce n'est normalement pas possible car les méthodes statiques ne prennent pas en charge les appels aux objets dynamiques. Il va donc fallir déclarer statique une variable du type de la fenêtre qui contient l'objet à manipuler, puis à affecter le handle de la fenêtre à cet objet lors de l'appel au constructeur de la WinForm, et ensuite manipuler le contrôle en spécifiant toute la chaîne de hiérarchie du contrôle, depuis le WinForm principal. Ce sont trois lignes dans le code...


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices; // Pour l'import des DLLs

/*
 * Utilisation des DLL par les fonctions Interop de C#.
 * Programme d'exemple permettant d'interpréter les informations parfois complexes du MSDN
 * et les mettre en situation avec un programme fonctionnel.
 */
/*
 * Utilisation de la fonction EnumWindows() pour lister toutes les fenêtres des programmes en cours
 * d'exécution dans le système. Voici ce que nous dit le MSDN :
 * Enumerates all top-level windows on the screen by passing the handle to each window, in turn,
 * to an application-defined callback function. EnumWindows continues until the last top-level
 * window is enumerated or the callback function returns FALSE
 *
 * C++ SYNTAX :
 *    BOOL WINAPI EnumWindows(
 *        _In_ WNDENUMPROC lpEnumFunc,
 *        _In_ LPARAM      lParam
 *    );
 *
 * PARAMETERS
 * lpEnumFunc [in]
 *   Type: WNDENUMPROC
 *     A pointer to an application-defined callback function. For more information, see EnumWindowsProc. 
 * lParam [in]
 *   Type: LPARAM
 *     An application-defined value to be passed to the callback function. 
 *
 * If the function succeeds, the return value is nonzero.
 *
 * If the function fails, the return value is zero. To get extended error information, call GetLastError.
 *
 * If EnumWindowsProc returns zero, the return value is also zero. In this case, the callback function
 * should call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows.
 *
 * Remarks
 *
 * The EnumWindows function does not enumerate child windows, with the exception of a few top-level
 * windows owned by the system that have the WS_CHILD style.
 *
 * This function is more reliable than calling the GetWindow function in a loop. An application that
 * calls GetWindow to perform this task risks being caught in an infinite loop or referencing a handle
 * to a window that has been destroyed. 
 *
 * Note  For Windows 8 and later, EnumWindows enumerates only top-level windows of desktop apps.
 */
/*
 * Header  : Winuser.h (include Windows.h)
 * Library : User32.lib
 * DLL     : User32.dll 
 */

/*
 * Dans la description précédente, le MSDN nous informe que la fonction EnumWindows() demande une
 * fonction callback EnumWindowsProc(). Toujours selon le MSDN, voici ce que nous devons savoir sur
 * cette fonction :
 * 
 * An application-defined callback function used with the EnumWindows or EnumDesktopWindows function.
 * It receives top-level window handles. The WNDENUMPROC type defines a pointer to this callback function.
 * EnumWindowsProc is a placeholder for the application-defined function name. 
 *
 * Syntax
 *  BOOL CALLBACK EnumWindowsProc(
 *    _In_ HWND   hwnd,
 *    _In_ LPARAM lParam
 *  );
 *
 * Parameters
 *   hwnd [in]
 *     A handle to a top-level window. 
 *   lParam [in]
 *     The application-defined value given in EnumWindows or EnumDesktopWindows. 
 *
 * Return value
 *
 * To continue enumeration, the callback function must return TRUE; to stop enumeration, it must
 * return FALSE. 
 *
 * Remarks
 *
 * An application must register this callback function by passing its address to EnumWindows or
 * EnumDesktopWindows. 
 */

namespace ListWindows
{
    public partial class frmMain : Form
    {
        static frmMain frm; // pour dialoguer avec les contrôles depuis la méthode statique

        protected delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lparam);
        [DllImport("user32.dll")]
        protected static extern bool EnumWindows(EnumWindowsProc enumwindows, IntPtr lparam);
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        protected static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        protected static extern int GetWindowTextLength(IntPtr hWnd);
        [DllImport("user32.dll")]
        protected static extern bool IsWindowVisible(IntPtr hWnd);

        protected static bool EnumTheWindows(IntPtr hWnd, IntPtr lparam)
        {
            int size = GetWindowTextLength(hWnd);
            if (size++ > 0 && IsWindowVisible(hWnd))
            {
                StringBuilder sb = new StringBuilder(size);
                GetWindowText(hWnd, sb, size);
                /*
                 * Puisque nous sommes dans une méthode statique, il nous est normalement impossible
                 * de communiquer avec des objets dynamiques tels que les contrôles Windows. Cependant,
                 * il est possible de contourner le problème en créant une variable statique de classe
                 * du type de la fenêtre possédant le contrôle à manipuler (ici frmMain). Il suffit ensuite
                 * de lui affecter this dans le constructeur de l'application et de faire référence aux
                 * différents contrôles par frm.
                 */
                frm.lst.Items.Add(sb.ToString());
            }
            return true; 
        }

        public frmMain()
        {
            InitializeComponent();
            frm = this; // Récupération du handle de la fenêtre qui contient les contrôles à manipuler
            AllWindows();
        }

        private void AllWindows()
        {
            EnumWindows(new EnumWindowsProc(EnumTheWindows), IntPtr.Zero);
        }

        private void frmMain_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape) this.Close();
        }
    }
}
      
© LGB - 201x+