Il existe déjà de nombreux articles sur la toile expliquant comment contrôler une application s'exécutant sur une machine et ce par l'envoi de message Windows. Il s'agit ici de faire un bref rappel sur une technique souvent utilisée lors du développement d'application en code managé : le P/Invoke (Platform Invoke). Nous illustrerons ceci avec une manipulation simple de l'explorateur Windows.

Rappels :

Qu'est-ce que le Platform-Invoke :

Plus connu sous sa forme raccourcie « P/Invoke », le Platform-Invoke est une technique permettant d'utiliser l'api Win32 non managée dans du code managé. En d'autres termes, il s'agit d'importer des fonctions existant dans des bibliothèques système (kernel32.dll, shell32.dll …) afin de les utiliser dans une application .NET.

Source : http://technet.microsoft.com/en-us/library/bb497008(TechNet.10).aspx

En premier lieu, le service P/Invoke du CLR (Common Language Runtime) va charger la DLL ciblée en mémoire, localiser la fonction cible et dépiler les arguments à lui passer, par l'intermédiaire du « Standard marshaling service ». En second lieu, la méthode est exécutée et retourne le résultat, toujours par l'intermédiaire du même service suscité.

Je vous invite vivement à regarder la correspondance des types des arguments qui peuvent être passés aux fonctions via P/Invoke sur cette page.

Tous les outils nécessaires sont présents dans le namespace « System.Runtime.InteropServices ». Intéressons nous rapidement à ce dernier.

Le namespace « System.Runtime.InteropServices » :

Ce namespace fournit tous les outils pour inter opérer avec les services COM et réaliser du platform invoke. Dans notre cas, la classe de ce namespace qui nous intéresse le plus est « DllImportAttribute ».

Une méthode précédée de cet attribut est indiquée comme étant le point d'entrée statique vers une fonction précise d'une DLL (dynamic link library).

Exemple :

[DllImport("user32.dll")]

static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

Dans cet exemple, nous déclarons la méthode ShowWindow comme point d'entrée statique vers la fonction non-managée du même nom de la librairie user32.dll.

Si vous cherchez la définition C# d'une méthode système, d'une énumération, d'une structure … je vous conseil de visiter le site web PInvoke.net, véritable bible du P/Invoke !

Après ces quelques rappels élémentaires sur la technique de platform-invoke, voyons comment communiquer avec un autre programme à l'aide de cette technique.

Communication par messages Windows

Avant tout, il faut savoir qu'il existe plusieurs façons de communiquer entre 2 processus distincts. Ici, nous utiliseront une méthode permettant tout simplement d'envoyer des messages Windows dans la queue de la boucle de messages d'une application ciblée. Celle-ci doit bien évidement avoir été développée afin d'intercepter et interpréter les messages qui lui sont envoyés.

Pour ce faire nous devons, avant tout, récupérer le « handle (hwnd) » de la fenêtre à laquelle nous souhaitons envoyer le message. Le Handle n'est rien d'autre qu'un identifiant unique (sous forme d'entier ou IntPtr) d'un controle sous Windows. Nous utiliserons pour ceci la méthode « FindWindow » présente dans la dll User32.dll :

[DllImport("user32.dll", SetLastError = true)]

static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

Ici, la propriété SetLastError, nous permet de préciser que si une erreur se produit lors de l'appel de la méthode, la fonction SetLastError de l'API Win32 soit appelée avant que la méthode FindWindow retourne. Ainsi, nous pourrons, par l'intermédiaire de la function GetLastError, récupérer le code de l'erreur qui s'est produite.

Pour récupérer la fenêtre, nous devons connaître soit :

  • La classe de la fenêtre ciblée (paramètre lpClassName)
  • Le titre de la fenêtre ciblée (paramètre lpWindowName)
  • Les deux précédents.

Par exemple, pour l'explorateur Windows, il suffirait de connaître l'emplacement qui est explorer afin de récupérer la fenêtre.

Exemple :

Créeons une application console, dans laquelle nous lançons l'explorateur Windows, qui par défaut va ouvrir vos documents (ayant pour titre « Documents », sous Vista) :

class Program

{

static void Main(string[] args)

{

Process explorer = Process.Start("explorer.exe");

}

}

Jusqu'ici, rien de bien compliqué : nous utilisons la classe Process du namespace System.Diagnostics, permettant de lancer le processus.

Ajoutons à notre application l'import de la méthode FindWindow vu plus haut et récupérons le handle de la fenêtre ouverte :

class Program

{

[DllImport("user32.dll", SetLastError = true)]

static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

static void Main(string[] args)

{

Process explorer = Process.Start("explorer.exe");

Thread.Sleep(2000);

IntPtr explorer_handle = FindWindow(null, "Documents");

}

}

Si nous exécutons cette application en Debug, nous pouvons constater que nous avons bien récupérer un entier pour la fenêtre recherchée :

Nous avons donc écrit le code permettant de récupérer l'identifiant unique de la fenêtre avec laquelle nous souhaitons communiquer. Nous allons à présent voir comment lui envoyer des messages Windows, et ce par l'intermédiaire de la fonction SendMessage, dont la signature C# est :

[DllImport("user32.dll")]

public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);

Comme vous pouvez le voir, cette méthode prend 4 paramètres :

  • hWnd : le handle de la fenêtre que nous avons récupéré plus haut, afin de préciser à quelle application le message doit être envoyé.
  • Msg : le code du message Windows à envoyer.
  • wParam : le premier paramètre du message Windows
  • lParam : le second paramètre du message Windows (0 en général).

En effet, un message Windows se décompose en deux parties :

  • La commande à proprement dite, i.e. le numéro du message Windows à envoyer (Msg)
  • Les paramètres de la commande (wParam et lParam).

Par exemple, si nous souhaitons fermer la fenêtre de l'explorateur Windows, nous devrons envoyer une commande système, ayant pour identifiant :

private const int WM_SYSCOMMAND = 0x0112;

Mais nous devons préciser que cette commande système agit dans le but de fermer la fenêtre (wParam) :

private const int WM_CLOSE = 0xF060;

Ce qui nous donne au final pour notre application :

class Program

{

[DllImport("user32.dll", SetLastError = true)]

static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll")]

public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);

private const int WM_SYSCOMMAND = 0x0112;

private const int WM_CLOSE = 0xF060;

static void Main(string[] args)

{

Process explorer = Process.Start("explorer.exe");

Thread.Sleep(2000);

Console.WriteLine("Récupération du handle pour la fenêtre Documents...");

IntPtr explorer_handle = FindWindow(null, "Documents");

Console.WriteLine(string.Format("Handle de la fenêtre récupéré : {0}", explorer_handle.ToString()));

Thread.Sleep(2000);

SendMessage((int)explorer_handle, WM_SYSCOMMAND, WM_CLOSE, 0);

Console.WriteLine("Fenêtre fermée!");

Console.Read();

}

}

En exécutant le code, vous remarquerez effectivement que la fenêtre de l'explorateur s'est fermée.

Il s'agit ici d'un exemple basique de ce que l'on peut faire en platform-invoke. Bien évidement, je ne vais pas vous détailler l'utilisation des nombreuses fonctions disponibles dans l'API Win32, chose d'ailleurs très bien faite sur PInvoke.net.

Si vous avez lu mon poste précédent, sachez que la technique présentée ci-dessus peut être utilisée pour contrôler le Windows Media Player, à partir d'un PDA par exemple ;). Il s'agit juste de ne plus intercepter les messages dans votre application, mais de les envoyer au Windows MediaPlayer à l'aide de SendMessage, après avoir récupérer son Handle à l'aide de FindWindow.

Allez plus loin dans P/Invoke : http://msdn.microsoft.com/en-us/magazine/cc164123.aspx

A bientôt !

Au total, 0 commentaire(s) posté(s) ! Poster un commentaire

comments Ajouter un commentaire

(Affichera votre icône Gravatar)  
[b][/b] - [i][/i] - [u][/u]- [quote][/quote]