Version imprimable      Envoyer     
Cliquez pour évaluer et commenter
MSDN
MSDN Library
Articles Techniques
Visual Studio 2005
C#
 Utilisation de P/Invoke pour appele...
Utilisation de P/Invoke pour appeler des API non gérées à partir de vos classes gérées
Paru le 01 mars 2006
Par Microsoft Corporation

Résumé : Apprenez à utiliser P/Invoke pour appeler des API Win32 non gérées à partir de code géré. Ces exemples d'applications illustrent comment couper le son, changer la résolution de Windows et afficher des info-bulles à partir de votre code géré. (33 pages imprimées)

Cliquez pour télécharger pinvokesamples.msi.

Cliquez pour lire l'article en anglais .

Introduction

La bibliothèque de classes de base (BCL, Base Class Library) du .NET Framework (BCL) est constituée de milliers de classes qui couvrent presque toutes les fonctionnalités requises pour créer des applications puissantes. Certaines fonctionnalités, telles que le changement de résolution de Windows ou l'activation/la désactivation du son, ne peuvent être effectuées qu'avec des appels d'API ou de fonctions Win32 dans d'autres DLL (Dynamic Link Libraries) gérées. Le code non géré est celui qui réside en dehors du CLR (Common Language Runtime). Les composants COM et ActiveX écrits en C ou C++, ainsi que les fonctions des API Win32, en sont des exemples.

Les exemples de cet article et l'exemple de code permettent de désactiver/réactiver le son, de changer la résolution Windows, d'afficher des info-bulles, de déterminer l'état d'alimentation de votre ordinateur, de changer le papier peint et d'extraire les icônes des fichiers DLL ou .exe. Dans la mesure où cet article a d'abord été écrit, l'affichage des info-bulles a été ajouté à la prise en charge de la BCL, mais l'exemple est conservé ici à des informations d'information. En utilisant ces exemples comme point de départ, vous devriez pouvoir utiliser les fonctionnalités intéressantes de l'API Win32 ou d'autres DLL non gérées qui ne sont pas exposées via la BCL. Les échantillons de code de chaque exemple de cet article ne sont que des extraits du code complet utilisé dans chaque échantillon et sont utilisés pour illustrer une étape particulière. Le code complet est fourni dans un ensemble d'applications téléchargeables.

Qu'est-ce que P/Invoke ?

P/Invoke est l'abréviation de Platform Invoke et offre les fonctionnalités permettant d'accéder aux fonctions, structures et rappels dans les DLL non gérées. P/Invoke offre une couche de traduction permettant d'aider les développeurs en les autorisant à étendre la bibliothèque des fonctionnalités disponibles, au-delà de la bibliothèque gérée de la BCL. Le moyen le plus simple de comprendre comment utiliser P/Invoke consiste à examiner des exemples d'applications qui utilisent P/Invoke pour accomplir une tâche qui n'est pas possible avec du code géré.

Présentation des exemples

Maintenant que nous en savons un peu plus sur P/Invoke, examinons l'exemple de code qui accompagne cet article. Chaque échantillon est organisé de manière similaire et est conçu pour être ajouté et utilisé dans votre application sans trop de modifications. Toutes les déclarations de fonctions externes, structures et définitions de classe utilisées par ces fonctions résident dans une classe nommée NativeMethods. Une classe unique offre un wrapper pour tous les appels P/Invoke et est nommé de la même façon que l'échantillon (par exemple Balloon dans l'échantillon Balloon). Chaque échantillon inclut également toutes les classes et énumérations dans des fichiers de classe distincts et dans un formulaire Windows qui indique comment utiliser la fonctionnalité spécifique offerte par le code P/Invoke dans une application réelle (excepté l'échantillon Icon, utilisé dans l'échantillon Power).

Tâche 1 : Désactiver et réactiver le son

L'échantillon de désactivation/réactivation du son offre un moyen permettant de désactiver et de réactiver le son d'un ordinateur par programmation. Il utilise pour cela un certain nombre de fonctions contenues dans le fichier winmm.dll (mm signifie multimédia). Dans la mesure où il n'existe pas de fonctions permettant de désactiver ou de réactiver le son, nous pouvons les créer à l'aide des commandes xxxGetVolume et xxxSetVolume pour les divers flux sonores. Les trois flux sonores accessibles via le fichier winmm.dll sont wave, midi et aux.

Cet échantillon est l'échantillon P/Invoke le plus simple de cet article, car il ne nécessite pas la définition de structures ou de classes supplémentaires.

Figure 1

Utilisation

Après l'ajout de l'échantillon de code à votre application, l'utilisation de la fonctionnalité de désactivation et réactivation du son est très simple. Commencez par créer un objet SoundDevice en transmettant l'un des trois types de flux sonore possibles : wave, aux ou midi. Ensuite, appelez simplement les méthodes Mute et UnMute. Vous pouvez également configurer la propriété Volume de SoundDevice afin d'ajuster le volume pour chacun des flux sonores.

[C#]

SoundDevice wave = new SoundDevice(DeviceTypes.Wave);

wave.Mute();

wave.UnMute();

wave.Volumne = 5;

Visual Basic

Dim wave As New SoundDevice(DeviceTypes.Wave)

wave.Mute()

wave.UnMute()

wave.Volumne = 5

Déclarations

La première étape consiste à identifier la DLL et les fonctions gérées que nous allons utiliser. Pour le type de flux wave, les deux fonctions sont waveOutGetVolume et waveOutSetVolume. Ces deux fonctions résident dans winmm.dll et leurs signatures non gérées sont les suivantes :

MMRESULT waveOutGetVolume(
  HWAVEOUT hwo,      
  LPDWORD pdwVolume  
);

MMRESULT waveOutSetVolume(
  HWAVEOUT hwo,  
  DWORD dwVolume 
);

Nous utilisons également des fonctions similaires pour les périphériques aux et midi.

Un autre problème, un peu plus complexe, à résoudre pour accéder à ces fonctions à partir de code géré est de déterminer comment mettre en correspondance les paramètres et les valeurs renvoyées, tels que LPDWORD et DWORD, avec les types gérés. Vous pouvez facilement faire cela en vous référant au tableau de conversion de type fourni dans la section Informations complémentaires à la fin de cet article. Ce tableau indique que les types LPDWORD et DWORD peuvent être réprésentés par le type géré UInt32. Le type HWAVEOUT est simplement un pointeur (HANDLE) vers un périphérique de sortie audio. Le tableau indique que les pointeurs sont représentés par le type géré IntPtr. En consultant la documentation ou les fichiers d'en-tête de ces API, nous découvrons que le type MMRESULT est une valeur entière, avec l'une des valeurs MMSYSERR_ comme valeur renvoyée par la fonction. Du côté géré, nous convertissons ces entiers « magiques » en constantes qui clarifient leur signification. La plupart du temps, vous devez soit convertir les valeurs renvoyées en constantes, soit – mieux encore – en une énumération afin d'identifier leur signification dans votre code géré. Dans l'échantillon de désactivation/réactivation du son, nous avons défini les valeurs de retour possibles dans l'ensemble de constantes MMSYSERR_.

Maintenant que nous disposons des informations dont nous avons besoin, nous déclarons les wrappers de la fonction P/Invoke gérée de la façon suivante :

[C#]

[DllImport("winmm.dll")]
public static extern int waveOutGetVolume(IntPtr hwo, out uint dwVolume);

[DllImport("winmm.dll")]
public static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);

[Visual Basic]

<DllImport("winmm.dll")> _
Public Shared Function waveOutGetVolume(ByVal hwo As IntPtr, _
    ByRef dwVolume As System.UInt32) As Integer
End Function

<DllImport("winmm.dll")> _
Public Shared Function waveOutSetVolume(ByVal hwo As IntPtr, 
    ByVal dwVolume As System.UInt32) As Integer
End Function

Comme nous l'avons vu précédemment, ces déclarations résident dans la classe NativeMethods.

Implémentation

Ces fonctions sont appelées à partir des méthodes GetVolume et SetVolume de la classe SoundDevice. Cette classe offre un wrapper simple d'utilisation autour des fonctions externes dans la classe NativeMethods. L'encapsulation de l'accès aux fonctions P/Invoke est une méthode recommandée, car elle offre une séparation claire entre les fonctionnalités gérées et non gérées. Vous pouvez également séparer tout le traitement d'erreur concernant le code non géré dans les classes wrapper et générer plutôt des exceptions CLR.

[C#]

private int GetVolume(ref ushort volLeft, ref ushort volRight)
{
    uint vol = 0;
    int result = 9999;

    switch (_type)
    {
    case DeviceTypes.Wave:
        result = NativeMethods.waveOutGetVolume(_hwo, out vol);
        break;
    case DeviceTypes.Aux:
        result = NativeMethods.auxGetVolume(_deviceID, out vol);
        break;
    case DeviceTypes.Midi:
        result = NativeMethods.midiOutGetVolume(_hwo, out vol);
        break;
    }

    if (result != NativeMethods.MMSYSERR_NOERROR)
        return result;

    volLeft = (ushort)(vol & 0x0000ffff);
    volRight = (ushort)(vol >> 16);
    return NativeMethods.MMSYSERR_NOERROR;
}

private int SetVolume(ushort volLeft, ushort volRight)
{
    uint vol = ((uint)volLeft & 0x0000ffff) | ((uint)volRight << 16);

    switch (_type)
    {
    case DeviceTypes.Wave:
        return NativeMethods.waveOutSetVolume(_hwo, vol);
    case DeviceTypes.Aux:
        return NativeMethods.auxSetVolume(_deviceID, vol);
    case DeviceTypes.Midi:
        return NativeMethods.midiOutSetVolume(_hwo, vol);
    }
    return 0;
}

[Visual Basic]

Private Function GetVolume(ByRef volLeft As System.UInt16, _
    ByRef volRight As System.UInt16) As Integer

    Dim vol As System.UInt32 = 0
    Dim result As Integer = 9999

    Select Case m_type
        Case DeviceTypes.Wave
            result = NativeMethods.waveOutGetVolume(m_hwo, vol)
        Case DeviceTypes.Aux
            result = NativeMethods.auxGetVolume(m_deviceID, vol)
        Case DeviceTypes.Midi
            result = NativeMethods.midiOutGetVolume(m_hwo, vol)
    End Select

    If Not (result = NativeMethods.MMSYSERR_NOERROR) Then
        Return result
    End If

    volLeft = CType((vol And &HFFFF), System.UInt16)
    volRight = CType((vol >> 16), System.UInt16)

    Return NativeMethods.MMSYSERR_NOERROR

End Function

Private Function SetVolume(ByVal volLeft As System.UInt16, _
    ByVal volRight As System.UInt16) As Integer

    Dim vol As System.UInt32 = (CType(volLeft, System.UInt32) And 65535) _
        Or (CType(volRight, System.UInt32) << 16)

    Select Case m_type
        Case DeviceTypes.Wave
            Return NativeMethods.waveOutSetVolume(m_hwo, vol)
        Case DeviceTypes.Aux
            Return NativeMethods.auxSetVolume(m_deviceID, vol)
        Case DeviceTypes.Midi
            Return NativeMethods.midiOutSetVolume(m_hwo, vol)
    End Select

    Return 0

End Function

La désactivation du son revient à mettre le volume à zéro. Pour pouvoir mettre le volume à zéro, nous devons cependant d'abord stocker le volume actuel, de façon à pouvoir réinitialiser le niveau du volume lorsque nous réactivons le son. La réactivation redéfinit simplement le volume à la valeur stockée à l'étape de désactivation. Ces deux fonctions sont détaillées dans les méthodes Mute et UnMute.

[C#]

public void Mute()
{
    _leftVol = 0;
    _rightVol = 0;

    // First store the current volume settings
    int returnValue = GetVolume(ref _leftVol, ref _rightVol);

    // If that was successful then set the volume to zero
    if (returnValue == NativeMethods.MMSYSERR_NOERROR)
    {
        returnValue = SetVolume(0, 0);

        if (returnValue == NativeMethods.MMSYSERR_NOERROR)
        {
            // all is ok
        }
        else
        {
            MessageBox.Show("Could not set the volume to zero", 
                "Sound Sample", 
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    else
    {
         MessageBox.Show("Could not get the current volume setting", 
            "Sound Sample", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

public void UnMute()
{
    if (_leftVol > 0 || _rightVol > 0)
    {
        int returnValue = SetVolume(_leftVol, _rightVol);

        if (returnValue == NativeMethods.MMSYSERR_NOERROR)
        {
            // all is ok
        }
        else
        {
            MessageBox.Show("Could not unmute the sound", 
                "Sound Sample",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

[Visual Basic]

Public Sub Mute()
    m_leftVol = 0
    m_rightVol = 0

    Dim returnValue As Integer = GetVolume(m_leftVol, m_rightVol)

    If returnValue = NativeMethods.MMSYSERR_NOERROR Then
        returnValue = SetVolume(0, 0)
        If returnValue = NativeMethods.MMSYSERR_NOERROR Then
            ' all is ok
        Else
            MessageBox.Show("Could not set the volume to zero", _
                "Sound Sample", _
                MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    Else
        MessageBox.Show("Could not get the current volume setting", _
            "Sound Sample", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End If
End Sub

Public Sub UnMute()
    If m_leftVol > 0 OrElse m_rightVol > 0 Then

        Dim returnValue As Integer = SetVolume(m_leftVol, m_rightVol)

        If returnValue = NativeMethods.MMSYSERR_NOERROR Then
            ' all is ok
        Else
            MessageBox.Show("Could not unmute the sound", _
                "Sound Sample", _
                MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End If
End Sub

Outre la désactivation et la réactivation du son, cet exemple vous permet d'ajuster le volume à l'aide du curseur, lequel appelle également les méthodes xxxGetVolume et xxxSetVolume.

Cet exemple constitue probablement l'utilisation la plus simple qui soit de P/Invoke. Excepté la déclaration des fonctions comme externes (en C#) ou partagées (en Visual Basic) et le référencement de l'espace de noms System.Runtime.InteropServices, aucune autre action n'est requise pour commencer à utiliser la nouvelle fonctionnalité. L'essentiel du travail consiste à convertir les signatures de méthodes non gérées en signatures gérées. Un scénario plus courant implique l'utilisation de structures de classes qui sont envoyées et reçues, et qui contiennent les données requises pour exécuter les fonctions.

Heureusement, les sites tels que PInvoke.net offrent un large éventail de fonctions prédéfinies en C# et Visual Basic .NET, afin de faciliter ce travail. Cependant, à des fins d'explication, cet article crée tous ces wrappers de toutes pièces.

Tâche 2 : Changement de la résolution d'affichage

L'exemple Résolution Windows utilise les fonctions EnumDisplaySettings et ChangeDisplaySettings de la bibliothèque user32.dll pour énumérer et changer par programmation les paramètres d'affichage. Il s'agit de la fonctionnalité à laquelle vous accédez généralement en cliquant avec le bouton droit sur le bureau, en sélectionnant Propriétés | Paramètres, puis en ajustant le curseur Résolution de l'écran.

Figure 2

Utilisation

Après l'instanciation de l'objet Display, vous pouvez extraire tous les types d'affichage disponibles et les stocker dans une liste DevMode en appelant la méthode GetDisplaySettings. Pour changer la résolution, transmettez simplement à la méthode ChangeSettings une structure DevMode contenant les nouveaux paramètres. Vous pouvez utiliser l'une des structures DevMode renvoyées dans l'appel GetDisplaySettings, ou créer une nouvelle structure.

[C#]

Display display = new Display();


List<DevMode> settings = display.GetDisplaySettings();

display.ChangeSettings(selectedMode);

[Visual Basic]

Dim display As New Display()

Dim settings As List(Of DevMode) = display.GetDisplaySettings()

display.ChangeSettings(selectedMode)

Déclarations

Les fonctions Win32 EnumDisplaySettings et ChangeDisplaySettings utilisent une structure DEVMODE contenant tous les paramètres propres au périphérique d'affichage ou d'impression. Cette structure est utilisée pour renvoyer les informations de paramètre dans la fonction EnumDisplaySettings et pour changer les paramètres dans la fonction ChangeDisplaySetting. La définition non gérée de la structure DEVMODE est la suivante :

typedef struct _devicemode { 
  BCHAR  dmDeviceName[CCHDEVICENAME]; 
  WORD   dmSpecVersion; 
  WORD   dmDriverVersion; 
  WORD   dmSize; 
  WORD   dmDriverExtra; 
  DWORD  dmFields; 
  union {
    struct {
      short dmOrientation;
      short dmPaperSize;
      short dmPaperLength;
      short dmPaperWidth;
      short dmScale; 
      short dmCopies; 
      short dmDefaultSource; 
      short dmPrintQuality; 
    };
    POINTL dmPosition;
    DWORD  dmDisplayOrientation;
    DWORD  dmDisplayFixedOutput;
  };

  short  dmColor; 
  short  dmDuplex; 
  short  dmYResolution; 
  short  dmTTOption; 
  short  dmCollate; 
  BYTE  dmFormName[CCHFORMNAME]; 
  WORD  dmLogPixels; 
  DWORD  dmBitsPerPel; 
  DWORD  dmPelsWidth; 
  DWORD  dmPelsHeight; 
  union {
    DWORD  dmDisplayFlags; 
    DWORD  dmNup;
  }
  DWORD  dmDisplayFrequency; 
#if(WINVER >= 0x0400) 
  DWORD  dmICMMethod;
  DWORD  dmICMIntent;
  DWORD  dmMediaType;
  DWORD  dmDitherType;
  DWORD  dmReserved1;
  DWORD  dmReserved2;
#if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400)
  DWORD  dmPanningWidth;
  DWORD  dmPanningHeight;
#endif
#endif /* WINVER >= 0x0400 */
} DEVMODE;

Nous n'avons pas besoins des unions et des sections de condition de prétraitement de la structure DEVMODE lors du traitement des paramètres d'affichage, donc nous supposons que ((WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400)) est vrai, ainsi que les portions de l'union qui s'appliquent aux périphériques d'affichage. La définition de structure gérée qui en résulte contient uniquement les informations dont nous avons besoin.

[C#]

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DevMode
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string dmDeviceName;

    public short dmSpecVersion;
    public short dmDriverVersion;
    public short dmSize;
    public short dmDriverExtra;
    public int dmFields;
    public int dmPositionX;
    public int dmPositionY;
    public int dmDisplayOrientation;
    public int dmDisplayFixedOutput;
    public short dmColor;
    public short dmDuplex;
    public short dmYResolution;
    public short dmTTOption;
    public short dmCollate;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string dmFormName;

    public short dmLogPixels;
    public short dmBitsPerPel;
    public int dmPelsWidth;
    public int dmPelsHeight;
    public int dmDisplayFlags;
    public int dmDisplayFrequency;
    public int dmICMMethod;
    public int dmICMIntent;
    public int dmMediaType;
    public int dmDitherType;
    public int dmReserved1;
    public int dmReserved2;
    public int dmPanningWidth;
    public int dmPanningHeight;
}

[Visual Basic]

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
    Public Structure DevMode

        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
        Public dmDeviceName As String

        Public dmSpecVersion As Short
        Public dmDriverVersion As Short
        Public dmSize As Short
        Public dmDriverExtra As Short
        Public dmFields As Integer
        Public dmPositionX As Integer
        Public dmPositionY As Integer
        Public dmDisplayOrientation As Integer
        Public dmDisplayFixedOutput As Integer
        Public dmColor As Short
        Public dmDuplex As Short
        Public dmYResolution As Short
        Public dmTTOption As Short
        Public dmCollate As Short

        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
        Public dmFormName As String

        Public dmLogPixels As Short
        Public dmBitsPerPel As Short
        Public dmPelsWidth As Integer
        Public dmPelsHeight As Integer
        Public dmDisplayFlags As Integer
        Public dmDisplayFrequency As Integer
        Public dmICMMethod As Integer
        Public dmICMIntent As Integer
        Public dmMediaType As Integer
        Public dmDitherType As Integer
        Public dmReserved1 As Integer
        Public dmReserved2 As Integer
        Public dmPanningWidth As Integer
        Public dmPanningHeight As Integer
    End Structure

La structure est décorée avec l'attribut StructLayout et le paramètre sequential pour la propriété Layout. Pour plus d'informations concernant l'attribut StructLayout, reportez-vous à la discussion dans la section Informations complémentaires à la fin de cet article. La plupart des types membres non gérés ont été facilement convertis en types gérés à l'aide du tableau de conversion de type. Seuls les champs dmDeviceName et dmFormName posent un problème, car il s'agit de tableaux de longueur fixe qui n'ont pas de correspondance directe avec un type géré. Lorsque nous rencontrons cette situation, nous pouvons utiliser la propriété UnmanagedType de l'attribut MarshalAs afin de définir le type comme l'un des types de l'énumération UnmanagedType. Le type ByValTStr est défini comme « un tableau de longueur fixe de caractères ; le type du tableau est déterminé par le jeu de caractères de la structure contenante ». En utilisant ce type et la propriété SizeConst pour définir la taille du tableau, nous pouvons définir de manière adéquate le type du regroupeur. Notez que la propriété CharSet définie dans StructLayout détermine également le jeu de caractères de ce type. Le paramètre par défaut est Char.Ansi, mais ce paramètre peut poser des problèmes de performances sous WIN NT et vous devez utiliser le paramètre CharSet.Auto chaque fois que cela est possible afin de laisser le regroupeur déterminer le meilleur jeu de caractères à utiliser.

Implémentation

Une fois que nous avons défini les fonctions et structures, le flux de l'application est relativement simple. Nous commençons par appeler la fonction GetSettings, laquelle appelle à son tour notre déclaration P/Invoke de EnumDisplaySettings. Un compteur est transmis comme paramètre iModeNum. Nous incrémentons ce compteur jusqu'à ce que la fonction renvoie 0, ce qui indique que tous les paramètres d'affichage disponibles pour l'ordinateur ont été renvoyés et stockés dans la liste générique de type DevMode.

[C#]

List<DevMode> modes = new List<DevMode>();
DevMode devmode = this.DevMode;
int counter = 0;
int returnValue = 1;

while (returnValue != 0)
{
    returnValue = GetSettings(ref devmode, counter++);
    modes.Add(devmode);
}

[Visual Basic]

Dim modes As New List(Of DevMode)
Dim devmode As DevMode = Me.DevMode
Dim counter As Integer = 0
Dim returnValue As Integer = 1

While returnValue <> 0
    returnValue = GetSettings(devmode, counter)
    modes.Add(devmode)
    counter = counter + 1
End While    

Pour changer le paramètre actuel, il nous suffit de transmettre à la fonction ChangeDisplaySettings une structure DevMode contenant les nouveaux paramètres. Nous avons stockés les différentes structures DevMode dans une liste, ce qui permet leur extraction facile. Nous comparons la valeur renvoyée à l'énumération ReturnCodes afin de déterminer le résultat de l'appel. Une fois encore, il est préférable de convertir les divers nombres « magiques » en énumérations ou en constantes dans le code géré afin de faciliter leur compréhension ; sinon, vous risquez de vous demander ultérieurement ce que signifie réellement le code de retour -5.

[C#]

public string ChangeSettings(DevMode devmode)
{
    string errorMessage = "";

    ReturnCodes iRet = NativeMethods.ChangeDisplaySettings(ref devmode, 0);

    switch (iRet)
    {
    case ReturnCodes.DISP_CHANGE_SUCCESSFUL:
        break;
    case ReturnCodes.DISP_CHANGE_RESTART:
        errorMessage = "Please restart your system";
        break;
    case ReturnCodes.DISP_CHANGE_FAILED:
        errorMessage = "ChangeDisplaySettigns API failed";
        break;
    case ReturnCodes.DISP_CHANGE_BADDUALVIEW:
        errorMessage = "The settings change was unsuccessful because " +
            "system is DualView capable.";
        break;
    case ReturnCodes.DISP_CHANGE_BADFLAGS:
        errorMessage = "An invalid set of flags was passed in.";
        break;
    case ReturnCodes.DISP_CHANGE_BADPARAM:
        errorMessage = "An invalid parameter was passed in. " +
            "This can include an invalid flag or combination of flags.";
        break;
    case ReturnCodes.DISP_CHANGE_NOTUPDATED:
        errorMessage = "Unable to write settings to the registry.";
        break;
    default:
        errorMessage 
            = "Unknown return value from ChangeDisplaySettings API";
        break;
    }
    return errorMessage;
}

[Visual Basic]

Public Function ChangeSettings(ByVal devmode As DevMode) As String
    Dim errorMessage As String = ""

    Dim iRet As ReturnCodes = NativeMethods.ChangeDisplaySettings(devmode, 0)

    Select Case iRet
    Case ReturnCodes.DISP_CHANGE_SUCCESSFUL
        Exit Function
    Case ReturnCodes.DISP_CHANGE_RESTART
        errorMessage = "Please restart your system"
        Exit Function
    Case ReturnCodes.DISP_CHANGE_FAILED
        errorMessage = "ChangeDisplaySettigns API failed"
        Exit Function
    Case ReturnCodes.DISP_CHANGE_BADDUALVIEW
        errorMessage = "The settings change was unsuccessful because " + _
            "system is DualView capable."
        Exit Function
    Case ReturnCodes.DISP_CHANGE_BADFLAGS
        errorMessage = "An invalid set of flags was passed in."
        Exit Function
    Case ReturnCodes.DISP_CHANGE_BADPARAM
        errorMessage = "An invalid parameter was passed in. " + _
            "This can include an invalid flag or combination of flags."
        Exit Function
    Case ReturnCodes.DISP_CHANGE_NOTUPDATED
        errorMessage = "Unable to write settings to the registry."
        Exit Function
    Case Else
        errorMessage = "Unknown return value from ChangeDisplaySettings API"
        Exit Function
    End Select
    Return errorMessage
End Function

Voilà tout pour cet échantillon P/Invoke. L'utilisation de structures pour transmettre des informations, plutôt qu'un grand nombre de paramètres indépendants, est le scénario le plus courant en code géré.

Pour notre dernier exemple, examinons une utilisation plus avancée de P/Invoke.

Tâche 3 : Affichage d'info-bulles

L'échantillon d'affichage d'info-bulles offre un moyen d'afficher les fenêtres de type Balloon utilisées dans Microsoft Windows. Ces bulles sont généralement utilisées pour la validation de saisie de données et s'affichent sur les champs comportant des valeurs manquantes ou incorrectes. L'exemple vous permet de changer l'alignement, l'icône, les positions et l'emplacement d'origine, afin de générer différentes bulles.

Figure 3

Utilisation

Vous pouvez créer une nouvelle bulle en créant simplement un objet Balloon, en définissant les valeurs appropriées des propriétés ParentControl, Title, Text, Icon, Position, Alignment, IsPostionAbsolute et IsStemCentered, puis en appelant la méthode Show.

Pour supprimer la bulle Balloon, appelez la méthode Dispose.

[C#]

Balloon balloon = new Balloon();
balloon.ParentControl = this.parentTextBox;
balloon.Title = "This is the balloon title";
balloon.Text = "This is where the balloon text appears";
balloon.Icon = BalloonIcon.Info;
balloon.Alignment = BalloonAlignment. TopMiddle;
balloon.IsPositionAbsolute = true;
balloon.IsStemCentered = false;

// show the balloon tip
balloon.Show();

// Hide the balloon tip
ballon.Dispose();

[Visual Basic]

Dim balloon as New Balloon();
balloon.ParentControl = this.parentTextBox;
balloon.Title = "This is the balloon title";
balloon.Text = "This is where the balloon text appears";
balloon.Icon = BalloonIcon.Info;
balloon.Alignment = BalloonAlignment. TopMiddle;
balloon.IsPositionAbsolute = true;
balloon.IsStemCentered = false;

' show the balloon tip
balloon.Show();

' Hide the balloon tip
ballon.Dispose();

Déclarations

Cet exemple utilise quatre fonctions API Win32 (SendMessage, GetClientRect, ClientToScreen et SetWindowPos) et deux structures (TOOLINFO et RECT) pour afficher la bulle.

[C#]

 [DllImport("User32", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam,
    IntPtr lParam);

[DllImport("User32", SetLastError = true)]
public static extern int GetClientRect(IntPtr hWnd, ref RECT lpRect);

[DllImport("User32", SetLastError = true)]
public static extern int ClientToScreen(IntPtr hWnd, ref RECT lpRect);

[DllImport("User32", SetLastError = true)]
public static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, 
    int X, int Y, int cx, int cy, int uFlags);

[StructLayout(LayoutKind.Sequential)]
public struct TOOLINFO
{
    public int cbSize;
    public int uFlags;
    public IntPtr hwnd;
    public IntPtr uId;
    public RECT rect;
    public IntPtr hinst;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string lpszText;
    public uint lParam;
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

[Visual Basic]

<DllImport("User32", SetLastError:=True)> _
Public Shared Function SendMessage(ByVal hWnd As IntPtr, _ 
    ByVal Msg As Integer, ByVal wParam As Integer, _
    ByVal lParam As IntPtr) As Integer
End Function

<DllImport("User32", SetLastError:=True)> _
Public Shared Function GetClientRect(ByVal hWnd As IntPtr, _ 
    ByRef lpRect As RECT) As Integer
End Function

<DllImport("User32", SetLastError:=True)> _
Public Shared Function ClientToScreen(ByVal hWnd As IntPtr, ByRef lpRect _
    As RECT) As Integer
End Function

<DllImport("User32", SetLastError:=True)> _
Public Shared Function SetWindowPos(ByVal hWnd As IntPtr, _
    ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, _
    ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, _
    ByVal uFlags As Integer) As Integer
End Function
<StructLayout(LayoutKind.Sequential)> _
Public Structure TOOLINFO
Public cbSize As Integer
Public uFlags As Integer
Public hwnd As IntPtr
Public uId As IntPtr
Public rect As RECT
Public hinst As IntPtr
<MarshalAs(UnmanagedType.LPTStr)> _
Public lpszText As String
Public lParam As System.UInt32
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure RECT
Public left As Integer
Public top As Integer
Public right As Integer
Public bottom As Integer
End Structure

Implémentation

Outre l'encapsulation de ces fonctions et structures d'API Win32, nous devons également créer BalloonWindow, une nouvelle classe qui hérite de la classe NativeWindow du .NET Framework. Cette classe offre une encapsulation de bas niveau d'un pointeur de fenêtre et d'une procédure de fenêtre. Nous utilisons cette classe pour déterminer si l'utilisateur a cliqué sur la bulle. Dans ce cas, un événement personnalisé (WindowClosed) est généré et provoque finalement la fermeture de la bulle. La méthode WndProc est appelée chaque fois qu'un message Windows est envoyé au pointeur de la fenêtre.

[C#]

internal class BalloonWindow : NativeWindow
{
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_LBUTTONDOWN)
        {
            if (WindowClosed != null)
                WindowClosed();
        }
        base.WndProc(ref m);
    }

    private const int WM_LBUTTONDOWN = 0x0201;
    public event BalloonWindowClosedEventHandler WindowClosed;
}

[Visual Basic]

Friend Class BalloonWindow
   Inherits NativeWindow
    Protected Overloads Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_LBUTTONDOWN Then
            RaiseEvent WindowClosed()
        End If
        MyBase.WndProc(m)
    End Sub

    Private Const WM_LBUTTONDOWN As Integer = &H201
    Public Event WindowClosed As BalloonWindowClosedEventHandler
End Class

Outre les constantes habituelles, cet exemple utilise également trois énumérations (BalloonAlignment, BalloonIcon et BalloonPosition) afin de nous aider à déterminer la position et l'icône utilisées pour la fenêtre. Tous les éléments de prise en charge étant définis, nous pouvons à présent créer la fenêtre de la bulle. La classe Balloon encapsule toutes les fonctionnalités requises. Elle offre les deux méthodes dont nous avons besoin pour afficher et fermer une fenêtre, ainsi qu'un certain nombre de propriétés utilisées pour décrire chaque fenêtre.

La méthode Show commence par créer une fenêtre avec la classe BalloonWindow. La méthode NativeWindow CreateHandle est essentiellement un wrapper autour de la fonction Win32 CreateWindowEx. Elle nécessite de transmettre un objet CreateParams qui définit les divers éléments de style de la nouvelle fenêtre.

[C#]

CreateParams createParams = new CreateParams();
createParams.ClassName = NativeMethods.TOOLTIPS_CLASS;
createParams.Style = NativeMethods.WS_POPUP | NativeMethods.TTS_BALLOON | 
    NativeMethods.TTS_NOPREFIX | NativeMethods.TTS_ALWAYSTIP | 
    NativeMethods.TTS_CLOSE;

_balloonWindow.CreateHandle(createParams);

[Visual Basic]

Dim createParams As CreateParams = New CreateParams
createParams.ClassName = NativeMethods.TOOLTIPS_CLASS
createParams.Style = NativeMethods.WS_POPUP Or NativeMethods.TTS_BALLOON _
    Or NativeMethods.TTS_NOPREFIX Or NativeMethods.TTS_ALWAYSTIP Or _
    NativeMethods.TTS_CLOSE

m_balloonWindow.CreateHandle(createParams)

L'étape suivante consiste à créer et à alimenter une structure TOOLINFO. Notez la façon dont nous utilisons la constante pour faciliter la compréhension des indicateurs.

[C#]

_toolInfo = new TOOLINFO();
_toolInfo.cbSize = Marshal.SizeOf(_toolInfo);
toolInfo.uFlags = NativeMethods.TTF_TRACK | NativeMethods.TTF_IDISHWND | 
    NativeMethods.TTF_TRANSPARENT | NativeMethods.TTF_SUBCLASS | 
    NativeMethods.TTF_PARSELINKS;

if (this.IsPositionAbsolute)
{
    toolInfo.uFlags |= NativeMethods.TTF_ABSOLUTE;
}
if (this._isStemCentered)
{
    toolInfo.uFlags |= NativeMethods.TTF_CENTERTIP;
}
_toolInfo.uId = _balloonWindow.Handle;
_toolInfo.lpszText = this.Text;
_toolInfo.hwnd = _parentControl.Handle;

[Visual Basic]

m_toolInfo = New TOOLINFO
m_toolInfo.cbSize = Marshal.SizeOf(m_toolInfo)
m_toolInfo.uFlags = NativeMethods.TTF_TRACK Or NativeMethods.TTF_IDISHWND _
    Or NativeMethods.TTF_TRANSPARENT Or NativeMethods.TTF_SUBCLASS Or _
    NativeMethods.TTF_PARSELINKS

If Me.IsPositionAbsolute Then
    m_toolInfo.uFlags = m_toolInfo.uFlags Or (NativeMethods.TTF_ABSOLUTE)
End If
If Me.m_isStemCentered Then
    m_toolInfo.uFlags = m_toolInfo.uFlags Or (NativeMethods.TTF_CENTERTIP)
End If
m_toolInfo.uId = m_balloonWindow.Handle
m_toolInfo.lpszText = Me.Text
m_toolInfo.hwnd = m_parentControl.Handle

La structure BalloonWindow et TOOLINFO étant définie, nous appelons trois API Win32 afin de définir la taille et la position de la fenêtre.

[C#]

NativeMethods.GetClientRect(_parentControl.Handle, ref _toolInfo.rect);
NativeMethods.ClientToScreen(_parentControl.Handle, ref _toolInfo.rect);
NativeMethods.SetWindowPos(_balloonWindow.Handle, 
    NativeMethods.HWND_TOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOACTIVATE | 
    NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOSIZE);

[Visual Basic]

NativeMethods.GetClientRect(m_parentControl.Handle, _
    m_toolInfo.rect)NativeMethods.ClientToScreen(m_parentControl.Handle, _
    m_toolInfo.rect)
NativeMethods.SetWindowPos(m_balloonWindow.Handle, _
    NativeMethods.HWND_TOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOACTIVATE _
    Or NativeMethods.SWP_NOMOVE Or NativeMethods.SWP_NOSIZE)

Ensuite, nous envoyons les informations requises à la fenêtre afin de la convertir d'une fenêtre ordinaire en fenêtre de type bulle. L'API Win32 SendMessage est souvent utilisée pour communiquer avec une fenêtre existante et est utilisée ici pour accomplir notre tâche. Nous transmettons d'abord la structure TOOLINFO, puis nous définissons la largeur maximale de la fenêtre, et enfin nous définissons le titre. La dernière étape consiste à vider explicitement la mémoire allouée dans les méthodes AllocHGlobal et StringToHGlobalAuto en appelant la méthode FreeHGlobal.

[C#]

IntPtr pointer = Marshal.AllocHGlobal(Marshal.SizeOf(_toolInfo));
Marshal.StructureToPtr(_toolInfo, pointer, true);
NativeMethods.SendMessage(_balloonWindow.Handle, 
    NativeMethods.TTM_ADDTOOL, 0, pointer);
toolInfo = (TOOLINFO)Marshal.PtrToStructure(pointer, typeof(TOOLINFO));

NativeMethods.SendMessage(_balloonWindow.Handle, 
    NativeMethods.TTM_SETMAXTIPWIDTH, 0, new IntPtr(_maxWidth));
IntPtr pointerTitle = Marshal.StringToHGlobalAuto(this.Title);

NativeMethods.SendMessage(_balloonWindow.Handle, 
    NativeMethods.TTM_SETTITLE, (int)this.Icon, pointerTitle);
this.SetBalloonPosition(_toolInfo.rect);

Marshal.FreeHGlobal(pointer);
Marshal.FreeHGlobal(pointerTitle);

[Visual Basic]

Dim pointer As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(m_toolInfo))
Marshal.StructureToPtr(m_toolInfo, pointer, True)
NativeMethods.SendMessage(m_balloonWindow.Handle, _
    NativeMethods.TTM_ADDTOOL, 0, pointer)
m_toolInfo = CType(Marshal.PtrToStructure(pointer, GetType(TOOLINFO)), _
    TOOLINFO)
NativeMethods.SendMessage(m_balloonWindow.Handle, _
    NativeMethods.TTM_SETMAXTIPWIDTH, 0, New IntPtr(m_maxWidth))

Dim pointerTitle As IntPtr = Marshal.StringToHGlobalAuto(Me.Title)
NativeMethods.SendMessage(m_balloonWindow.Handle, _
    NativeMethods.TTM_SETTITLE, CType(Me.Icon, Integer), pointerTitle)
Me.SetBalloonPosition(m_toolInfo.rect)
Marshal.FreeHGlobal(pointer)
Marshal.FreeHGlobal(pointerTitle)

La dernière étape consiste à afficher la fenêtre. Pour cela, le message TTM_TRACKACTIVATE est transmis à la fenêtre (contrôle d'édition) à laquelle la bulle est associée. Là encore, nous utilisons la fonction SendMessage. N'oubliez pas de libérer la mémoire allouée à l'aide de la méthode FreeHGlobal, afin d'éviter les pertes de mémoire.

[C#]

IntPtr pointer2 = Marshal.AllocHGlobal(Marshal.SizeOf(_toolInfo));
Marshal.StructureToPtr(_toolInfo, pointer2, true);
NativeMethods.SendMessage(_balloonWindow.Handle, _
    NativeMethods.TTM_TRACKACTIVATE, -1, pointer2);

Marshal.FreeHGlobal(pointer2);

[Visual Basic]

Dim pointer2 As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(m_toolInfo))
Marshal.StructureToPtr(m_toolInfo, pointer2, True)
NativeMethods.SendMessage(m_balloonWindow.Handle, _
    NativeMethods.TTM_TRACKACTIVATE, -1, pointer2)
Marshal.FreeHGlobal(pointer2)

Et voilà. Nous avons une bulle.

Heureusement, la fermeture de la fenêtre est beaucoup plus simple que l'ouverture. Nous appelons simplement la fonction SendMessage en transmettant la même constante TTM_TRACKACTIVATE, mais au lieu de -1, nous transmettons 0 pour le paramètre wParam.

[C#]

NativeMethods.SendMessage(_balloonWindow.Handle, 
    NativeMethods.TTM_TRACKACTIVATE, 0, pointer);

[Visual Basic]

NativeMethods.SendMessage(m_balloonWindow.Handle, _
    NativeMethods.TTM_TRACKACTIVATE, 0, pointer)

Informations complémentaires

Attribut DLLImport

La procédure élémentaire d'utilisation de P/Invoke implique toujours la création d'une fonction gérée décorée avec l'attribut DllImport. En C#, ces fonctions wrapper non gérées doivent être déclarées comme externes. En Visual Basic, elles sont déclarées comme partagées. Lorsque cette fonction est appelée, le CLR localise et charge les DLL requises, regroupe les paramètres, effectue l'appel, puis dégroupe les paramètres de sortie et les valeurs renvoyées, avant de revenir à l'application gérée.

[C#]

[DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", 
    CharSet = CharSet.Unicode, ExactSpelling = true, 
    CallingConvention = CallingConvention.StdCall)]
public static extern int ExtractIconEx(string sFile, int iIndex, 
    out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);

[Visual Basic]

<DllImport("Shell32.dll", EntryPoint:="ExtractIconExW", _
    CharSet:=CharSet.Unicode, ExactSpelling:=True, _
    CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function ExtractIconEx(ByVal sFile As String, _
    ByVal iIndex As Integer, ByRef piLargeVersion As IntPtr, _
    ByRef piSmallVersion As IntPtr, ByVal amountIcons As Integer) _
    As Integer
End Function

L'attribut DllImport est utilisé pour décorer une méthode gérée afin d'indiquer qu'elle utilise une bibliothèque de liaison dynamique (DLL) non gérée comme point d'entrée statique. Vous devez au minimum indiquer le nom de la DLL qui fournit le point d'entrée. Examinons chaque paramètre plus en détail.

  • Name : Nom de la DLL dans laquelle la fonction réside.

  • Entry Point : Nom ou ordinal de la fonction. Ce paramètre est utile lors de la création d'un alias de la déclaration externe afin d'éviter les conflits de noms.

  • SetLastError : Lorsque ce paramètre est défini sur true, la fonction COM appelle la fonction SetLastError avant de revenir à l'application. Cette valeur peut ensuite être lue par le code géré.

  • CharSet : Cet attribut détermine la façon dont les chaînes sont regroupées. La valeur par défaut CharSet.Auto convertit la chaîne en ANSI sur les systèmes d'exploitation Win 9X et en Unicode sous Windows NT, 2000 et XP.

  • Exact Spelling : Lorsque ce paramètre a la valeur false, le CLR est autorisé à rechercher dans la DLL des points d'entrée semblables à la valeur EntryPoint.

  • BestFitMapping : Active ou désactive un comportement d'optimisation lors de la conversion Unicode vers ANSI.

  • Calling Convention : Une valeur parmi cinq possibles. Le paramètre Winapi est le même que la convention d'appel par défaut de la plate-forme. Cela signifie que l'utilisation de Winapi et StdCall sur une plate-forme est identique.

  • Preserve Signature : Lorsque cette valeur est true, la signature de la fonction externe est identique à celle de la fonction non gérée.

  • ThrowOnUnmappableChar : Lorsque cette valeur est true, une exception est générée lorsqu'un caractère Unicode ne peut pas être représenté au format ANSI ; sinon, le caractère est converti en ? et aucune exception n'est générée.

[C#]

[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW",  SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);

[Visual Basic]

<DllImport("KERNEL32.DLL", EntryPoint := "MoveFileW", _
   SetLastError := True, CharSet := CharSet.Unicode, _
   ExactSpelling := True, _
   CallingConvention := CallingConvention.StdCall)> _
Public Shared Function MoveFile(src As String, dst As String) As Boolean
    ' Leave function empty - DLLImport attribute 
    ' forwards calls to MoveFile to MoveFileW in KERNEL32.DLL.
End Function
Regroupement

Le regroupement est l'action de conversion des données afin qu'elles puissent être transmises et analysées correctement entre les espaces de programme gérés et non gérés. Le regroupement est effectué lors de l'exécution via le service de regroupement du CLR. Lorsque vous utilisez P/Invoke, vous devez généralement regrouper des classes et des structures, et contrôler les détails du regroupement à l'aide d'un certain nombre d'attributs de l'espace de noms System.Runtime.InteropServices.

Conversions de type

Au cours du regroupement, l'une des principales étapes consiste à convertir les types non gérés en types gérés, et inversement. Le service de regroupement du CLR sait comment effectuer nombre de ces conversions, mais vous devez quand même connaître les correspondances entre les différents types lors de la conversion de la signature non gérée vers la fonction gérée. Vous pouvez utiliser le tableau de conversion suivant pour mettre en correpondance les différents types.

Tableau 1   

Type de données Windows

Type de données .NET

BOOL, BOOLEAN

Boolean ou Int32

BSTR

String

BYTE

Byte

CHAR

Char

DOUBLE

Double

DWORD/LPDWORD

Int32 or UInt32

FLOAT

Single

HANDLE (et tous les autres types de pointeur, tels que HFONT et HMENU)

IntPtr, UintPtr ou HandleRef

HRESULT

Int32 or UInt32

INT

Int32

LANGID

Int16 ou UInt16

LCID

Int32 or UInt32

LONG

Int32

LPARAM

IntPtr, UintPtr ou Object

LPCSTR

String

LPCTSTR

String

LPCWSTR

String

LPSTR

String ou StringBuilder*

LPTSTR

String ou StringBuilder

LPWSTR

String ou StringBuilder

LPVOID

IntPtr, UintPtr ou Object

LRESULT

IntPtr

SAFEARRAY

type de tableau .NET

SHORT

Int16

TCHAR

Char

UCHAR

SByte

UINT

Int32 or UInt32

ULONG

Int32 or UInt32

VARIANT

Object

VARIANT_BOOL

Boolean

WCHAR

Char

WORD

Int16 ou UInt16

WPARAM

IntPtr, UintPtr ou Object

Rappels

Outre l'appel de DLL non gérées à partir de code géré, vous pouvez utiliser P/Invoke pour que le code non géré rappelle du code géré. Cela permet de disposer d'un moyen asynchrone d'utiliser les ressources non gérées.

[C#]

delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, 
    IntPtr lParam);

[Visual Basic]

Delegate Function WndProc(ByVal hWnd As IntPtr, ByVal msg As Integer, _
     ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr

Attribut StructLayout

Outre les types simples répertoriés dans le tableau de conversion de type, de nombreuses fonctions non gérées utilisent des structures afin de transmettre plus efficacement les informations. L'attribut StructLayout vous permet de spécifier comment organiser les membres de données dans les portions gérées de la mémoire, afin qu'ils puissent être facilement référencés dans les portions de mémoire non gérées. Les paramètres sont les suivants :

  • Layout : Peut être l'un des types suivants, le premier étant le plus courant.

    • LayoutKind.Sequential

    • LayoutKind.Union

    • LayoutKind.Explicit

  • Pack : Cette valeur contrôle la façon dont les champs de données sont alignés en mémoire en définissant la taille de paquet de chaque champ. Les valeurs possibles sont 1, 2, 4, 8 et 16. La valeur par défaut est de 8, sauf pour les structures non gérées dont la taille de paquet par défaut est généralement de 4.

  • Charset : Cet attribut détermine la façon dont les chaînes sont regroupées. La valeur par défaut CharSet.Auto convertit la chaîne en ANSI sur les systèmes d'exploitation Win 9X et en Unicode sous Windows NT, 2000 et XP. (Identique à l'attribut DLLImport).

  • Size : Il s'agit de la taille de la structure ou de la classe, laquelle doit être au moins égale à la somme de la taille des membres.

[C#]

[StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
public class MySystemTime 
{
   [FieldOffset(0)]public ushort wYear; 
   [FieldOffset(2)]public ushort wMonth;
   [FieldOffset(4)]public ushort wDayOfWeek; 
   [FieldOffset(6)]public ushort wDay; 
   [FieldOffset(8)]public ushort wHour; 
   [FieldOffset(10)]public ushort wMinute; 
   [FieldOffset(12)]public ushort wSecond; 
   [FieldOffset(14)]public ushort wMilliseconds; 
}

[Visual Basic]

<StructLayout(LayoutKind.Explicit, Size := 16, CharSet := CharSet.Ansi)>  _
Public Class MySystemTime
   <FieldOffset(0)> Public wYear As Short
   <FieldOffset(2)> Public wMonth As Short
   <FieldOffset(4)> Public wDayOfWeek As Short
   <FieldOffset(6)> Public wDay As Short
   <FieldOffset(8)> Public wHour As Short
   <FieldOffset(10)> Public wMinute As Short
   <FieldOffset(12)> Public wSecond As Short
   <FieldOffset(14)> Public wMilliseconds As Short
End Class 'MySystemTime

Attribut FieldOffset

Lorsqu'une structure ou une classe utilise le paramètre de disposition explicite, chaque champ peut être décoré avec l'attribut FieldOffset. Il spécifie le décalage des champs, en octets, à partir du début de la structure ou de la classe. Il est possible de faire se chevaucher les champs, créant ainsi une structure de données d'union offrant une nouvelle valeur, en combinant les valeurs qui se chevauchent des champs distincts.

[C#]

[StructLayout(LayoutKind.Explicit)]
public class SYSTEM_INFO
{
    [FieldOffset(0)] public ulong OemId;
    [FieldOffset(4)] public ulong PageSize;
    [FieldOffset(16)] public ulong ActiveProcessorMask;
    [FieldOffset(20)] public ulong NumberOfProcessors;
    [FieldOffset(24)] public ulong ProcessorType;
}

[Visual Basic]

<StructLayout(LayoutKind.Explicit)> _
Public Class SYSTEM_INFO
    <FieldOffset(0)> Private OemId As System.UInt64
    <FieldOffset(4)> Private PageSize As System.UInt64
    <FieldOffset(16)> Private ActiveProcessorMask As System.UInt64
    <FieldOffset(20)> Private NumberOfProcessors As System.UInt64
    <FieldOffset(24)> Private ProcessorType As System.UInt64
End Class

Attribut MarshallAs

Il arrive que le type exact requis par la fonction non gérée n'existe pas dans le code géré et qu'il ne puisse pas être remplacé ou mis en correspondance avec un autre type géré. Dans ce cas, vous devez fournir manuellement au regroupeur les informations de type, à l'aide de l'attribut MarshallAs. L'attribut MarshallAs remplace le comportement de regroupement par défaut du service de regroupement du CLR, comme nous l'avons fait dans la structure TOOLINFO de l'exemple de bulle.

[C#]

//Applied to a parameter.
  public void M1 ([MarshalAs(UnmanagedType.LPWStr)]String msg);
//Applied to a field within a class.
  class MsgText {
    [MarshalAs(UnmanagedType.LPWStr)] Public String msg;
  }
//Applied to a return value.
[return: MarshalAs(UnmanagedType.LPWStr)]
public String GetMessage();

[Visual Basic]

'Applied to a parameter.
  Public Sub M1 (<MarshalAs(UnmanagedType.LPWStr)> msg As String)
'Applied to a field within a class.
  Class MsgText 
    <MarshalAs(UnmanagedType.LPWStr)> Public msg As String
  End Class
'Applied to a a return value.
  Public Function M2() As <MarshalAs(UnmanagedType.LPWStr)> String

Conclusion

L'utilisation de P/Invoke est un bon moyen d'étendre les fonctionnalités disponibles pour les développeurs. Avec P/Invoke, vous pouvez exploiter les fonctionnalités de l'API Win32 et d'autre code non géré, sans avoir à réinventer la roue dans le code géré. Cela vous permet de gagner du temps et de bénéficier de fonctionnalités robustes et testées, ce qui contribue à l'utilité globale de votre application.

Bien entendu, l'inconvénient est que l'essentiel des détails relatifs au regroupement et à la libération de la mémoire est subitement intégré au code géré lors de l'accès à des fonctions non gérées. Cela peut réduire la stabilité globale de votre application et augmenter le temps de développement ; soyez donc prudent.

Pour plus d'informations sur P/Invoke, reportez-vous à l'article Appel de DLL Win32 en C# avec P/Invoke dans le magazine MSDN en ligne.

© 2008 Microsoft Corporation. Tous droits réservés. Conditions d'utilisation  |  Marques  |  Confidentialité
Page view tracker