Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Le Common Language Runtime fournit des Platform Invocation Services, ou PInvoke, qui permettent au code managé d’appeler des fonctions de style C dans des bibliothèques dynamiques natives liées (DLL). Le même marshaling de données est utilisé pour l’interopérabilité COM avec le runtime et pour le mécanisme « It Just Works », ou IJW.
Pour plus d'informations, voir :
Les exemples de cette section illustrent les possibilités d’utilisation de PInvoke. PInvoke peut simplifier le marshaling de données personnalisé, car vous fournissez des informations de marshaling de manière déclarative dans les attributs au lieu d’avoir à écrire du code de marshaling procédural.
Note
La bibliothèque de marshaling fournit un autre moyen de marshaler des données entre des environnements natifs et managés de manière optimisée. Consultez Vue d’ensemble du marshaling dans C++ pour obtenir plus d’informations sur la bibliothèque de marshaling. La bibliothèque de marshaling n’est utilisable que pour les données, et non pour les fonctions.
PInvoke et l’attribut DllImport
L’exemple suivant illustre l’utilisation de PInvoke dans un programme Visual C++. La fonction native puts est définie dans msvcrt.dll. L’attribut DllImport est utilisé pour la déclaration de puts.
// platform_invocation_services.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String ^);
int main() {
String ^ pStr = "Hello World!";
puts(pStr);
}
L’exemple suivant reprend l’exemple précédent en utilisant IJW.
// platform_invocation_services_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
#include <stdio.h>
int main() {
String ^ pStr = "Hello World!";
char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer();
puts(pChars);
Marshal::FreeHGlobal((IntPtr)pChars);
}
Avantages de IJW
Il n’est pas nécessaire d’écrire des déclarations d’attribut
DLLImportpour les API non managées utilisées par le programme. Il suffit d’inclure le fichier d’en-tête et le lien vers la bibliothèque d’importation.Le mécanisme IJW est légèrement plus rapide (par exemple, les stubs IJW n’ont pas besoin de vérifier la nécessité d’épingler ou de copier des éléments de données, car cela est fait explicitement par le développeur).
Cela illustre clairement les problèmes de performances. Dans ce cas, le fait que vous traduisez une chaîne Unicode en chaîne ANSI et que vous disposez d’une allocation et d’une désallocation de mémoire standard. Dans ce cas, un développeur écrivant le code à l’aide de IJW se rend compte qu’il vaudra mieux appeler
_putwset utiliserPtrToStringCharsdans un souci de performance.Si vous appelez de nombreuses API non managées à l’aide des mêmes données, une étape de marshaling et le passage de la copie marshalée est beaucoup plus efficace qu’un re marshaling répété.
Inconvénients de IJW
Le marshaling doit être spécifié explicitement dans le code au lieu d’attributs (qui ont souvent des valeurs par défaut appropriées).
Le code de marshaling est inline, où il est plus envahissant dans le flux de la logique d’application.
Étant donné que les API de marshaling explicites retournent des types de
IntPtrpour la portabilité de 32 bits à 64 bits, vous devez utiliser des appels supplémentaires deToPointer.
La méthode spécifique exposée par C++ est la méthode la plus efficace et explicite, bien que plus complexe.
Si l’application utilise principalement des types de données non managés ou si elle appelle plus d’API non managées que les API .NET Framework, nous vous recommandons d’utiliser la fonctionnalité IJW. Pour appeler occasionnellement une API non managée dans une application principalement managée, le choix est plus subtil.
PInvoke avec les API Windows
PInvoke est pratique pour appeler des fonctions dans Windows.
Dans cet exemple, un programme Visual C++ interagit avec la fonction MessageBox qui fait partie de l’API Win32.
// platform_invocation_services_4.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String ^ pText, String ^ pCaption, unsigned int uType);
int main() {
String ^ pText = "Hello World! ";
String ^ pCaption = "PInvoke Test";
MessageBox(0, pText, pCaption, 0);
}
La sortie est une zone de message intitulée PInvoke Test et contenant le texte Hello World!.
Les informations de marshaling sont également utilisées par PInvoke pour rechercher des fonctions dans la DLL. Dans user32.dll il n’existe en fait aucune fonction MessageBox, mais CharSet=CharSet::Ansi permet à PInvoke d’utiliser MessageBoxA, la version ANSI, au lieu de MessageBoxW, qui est la version Unicode. En général, nous vous recommandons d’utiliser des versions Unicode d’API non managées, car cela élimine la surcharge de traduction du format Unicode natif des objets de chaîne .NET Framework vers ANSI.
Quand ne pas utiliser PInvoke
L’utilisation de PInvoke n’est pas appropriée pour toutes les fonctions de style C dans les DLL. Par exemple, supposons qu’il existe une fonction MakeSpecial dans mylib.dll déclarée comme suit :
char * MakeSpecial(char * pszString);
Si nous utilisons PInvoke dans une application Visual C++, nous pouvons écrire quelque chose de similaire à ce qui suit :
[DllImport("mylib")]
extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);
La difficulté ici est que nous ne pouvons pas supprimer la mémoire de la chaîne non managée retournée par MakeSpecial. D’autres fonctions appelées via PInvoke retournent un pointeur vers une mémoire tampon interne qui n’a pas besoin d’être libérée par l’utilisateur. Dans ce cas, l’utilisation de la fonctionnalité IJW est clairement la meilleure option.
Limites de PInvoke
Vous ne pouvez pas retourner le même pointeur à partir d’une fonction native que vous avez prise en tant que paramètre. Le retour du pointeur marshalé par PInvoke par une fonction native peut entraîner la corruption de la mémoire et des exceptions.
__declspec(dllexport)
char* fstringA(char* param) {
return param;
}
L’exemple suivant illustre ce problème et, même si la sortie donnée par le programme peut sembler correcte, elle provient de la mémoire libérée.
// platform_invocation_services_5.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
#include <limits.h>
ref struct MyPInvokeWrap {
public:
[ DllImport("user32.dll", EntryPoint = "CharLower", CharSet = CharSet::Ansi) ]
static String^ CharLower([In, Out] String ^);
};
int main() {
String ^ strout = "AabCc";
Console::WriteLine(strout);
strout = MyPInvokeWrap::CharLower(strout);
Console::WriteLine(strout);
}
Marshaling d'arguments
Avec PInvoke, aucun marshaling n’est nécessaire entre les types managés et les types C++ natifs primitifs dont la forme est la même. Par exemple, aucun marshaling n’est requis entre Int32 et int, ou entre Double et double.
Toutefois, vous devez marshaler les types qui n’ont pas la même forme. Cela inclut les types char, string et struct. Le tableau suivant présente les mappages utilisés par le marshaleur pour différents types :
| wtypes.h | Visual C++ | Visual C++ avec /clr | Common Language Runtime |
|---|---|---|---|
| HANDLE | void * | void * | IntPtr, UIntPtr |
| BYTE | unsigned char | unsigned char | Byte |
| SHORT | short | short | Int16 |
| WORD | unsigned short | unsigned short | UInt16 |
| INT | int | int | Int32 |
| UINT | nombre entier non signé | nombre entier non signé | UInt32 |
| LONG | long | long | Int32 |
| BOOL | long | bool | Booléen |
| DWORD | unsigned long | unsigned long | UInt32 |
| ULONG | unsigned long | unsigned long | UInt32 |
| CHAR | car | car | Char |
| LPSTR | char * | String ^ [in], StringBuilder ^ [in, out] | String ^ [in], StringBuilder ^ [in, out] |
| LPCSTR | const char * | String ^ | Chaîne |
| LPWSTR | wchar_t * | String ^ [in], StringBuilder ^ [in, out] | String ^ [in], StringBuilder ^ [in, out] |
| LPCWSTR | const wchar_t * | String ^ | Chaîne |
| FLOAT | float | float | Unique |
| DOUBLE | double | double | Double |
Le marshaleur épingle automatiquement la mémoire allouée sur le tas du runtime si son adresse est passée à une fonction non managée. L’épinglage empêche le récupérateur de mémoire de déplacer le bloc de mémoire alloué pendant le compactage.
Dans l’exemple présenté précédemment dans cette rubrique, le paramètre CharSet de DllImport spécifie la façon dont les chaînes managées doivent être marshalées. Dans ce cas, elles doivent être marshalées en chaînes ANSI pour le côté natif.
Vous pouvez spécifier des informations de marshaling pour des arguments individuels d’une fonction native à l’aide de l’attribut MarshalAs. Il existe plusieurs choix pour marshaler un argument String * : BStr, ANSIBStr, TBStr, LPStr, LPWStr et LPTStr. Le choix par défaut est LPStr.
Dans cet exemple, la chaîne est marshalée en tant que chaîne de caractères Unicode sur deux octets, LPWStr. La sortie est la première lettre de Hello World! car le deuxième octet de la chaîne marshalée est null et puts l’interprète comme marqueur de fin de chaîne.
// platform_invocation_services_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String ^);
int main() {
String ^ pStr = "Hello World!";
puts(pStr);
}
L’attribut MarshalAs se trouve dans l’espace de noms System::Runtime::InteropServices. L’attribut peut être utilisé avec d’autres types de données tels que des tableaux.
Comme mentionné précédemment dans la rubrique, la bibliothèque de marshaling fournit une nouvelle méthode optimisée de marshaling des données entre des environnements natifs et managés. Pour plus d’informations, consultez Vue d’ensemble du marshaling dans C++.
Considérations relatives aux performances
PInvoke a une surcharge comprise entre 10 et 30 instructions x86 par appel. En plus de ce coût fixe, le marshaling crée une surcharge supplémentaire. Les types blittables ayant la même représentation dans le code managé et non managé n’imputent pas de coût de marshaling. Par exemple, il n’y a aucun coût pour la traduction de int en Int32.
Pour améliorer vos performances, préférez un nombre moins élevé d’appels PInvoke marshalant autant de données que possible à un grand nombre d’appels marshalant moins de données.