Guide pratique pour marshaler des chaînes à l’aide de P/Invoke
Les fonctions natives qui acceptent des chaînes de style C peuvent être appelées à l’aide du type System::String
de chaîne CLR à l’aide de la prise en charge de l’appel de plateforme .NET Framework (P/Invoke). Nous vous encourageons à utiliser les fonctionnalités d’interopérabilité C++ au lieu de P/Invoke lorsque cela est possible. étant donné que P/Invoke fournit peu de rapports d’erreurs au moment de la compilation, n’est pas de type sécurisé et peut être fastidieux à implémenter. Si l’API non managée est empaquetée en tant que DLL et que le code source n’est pas disponible, P/Invoke est la seule option. Sinon, consultez Utilisation de l’interopérabilité C++ (P/Invoke implicite).
Les chaînes managées et non managées sont disposées différemment en mémoire. Par conséquent, le passage de chaînes gérées à des fonctions non managées nécessite que l’attribut MarshalAsAttribute demande au compilateur d’insérer les mécanismes de conversion requis pour marshaler correctement et en toute sécurité les données de chaîne.
Comme avec les fonctions qui utilisent uniquement des types de données intrinsèques, DllImportAttribute est utilisée pour déclarer des points d’entrée managés dans les fonctions natives. Les fonctions qui passent des chaînes peuvent utiliser un handle pour le String type au lieu de définir ces points d’entrée comme prenant des chaînes de style C. L’utilisation de ce type invite le compilateur à insérer du code qui effectue la conversion requise. Pour chaque argument de fonction dans une fonction non managée qui accepte une chaîne, utilisez l’attribut MarshalAsAttribute pour indiquer que l’objet String
doit être marshalé vers la fonction native en tant que chaîne de style C.
Le marshaleur encapsule l’appel à la fonction non managée dans une routine de wrapper masquée. La routine wrapper épingle et copie la chaîne managée dans une chaîne allouée localement dans le contexte non managé. La copie locale est ensuite passée à la fonction non managée. Lorsque la fonction non managée retourne, le wrapper supprime la ressource. Ou, s’il était sur la pile, il est récupéré lorsque le wrapper sort de l’étendue. La fonction non managée n’est pas responsable de cette mémoire. Le code non managé crée et supprime uniquement la mémoire dans le tas configuré par son propre CRT. Il n’y a donc jamais de problème avec le marshaller à l’aide d’une autre version CRT.
Si votre fonction non managée retourne une chaîne, sous la forme d’une valeur de retour ou d’un paramètre out, le marshaleur le copie dans une nouvelle chaîne managée, puis libère la mémoire. Pour plus d’informations, consultez Comportement de marshaling par défaut et marshaling des données avec appel de plateforme.
Exemple
Le code suivant se compose d’un module non managé et d’un module managé. Le module non managé est une DLL qui définit une fonction appelée TakesAString
. TakesAString
accepte une chaîne étroite de style C sous la forme d’un char*
.
// TraditionalDll2.cpp
// compile with: /LD /EHsc
#include <windows.h>
#include <stdio.h>
#include <iostream>
using namespace std;
#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif
extern "C" {
TRADITIONALDLL_API void TakesAString(char*);
}
void TakesAString(char* p) {
printf_s("[unmanaged] %s\n", p);
}
Le module managé est une application en ligne de commande qui importe la TakesAString
fonction, mais la définit comme prenant un module managé System.String
au lieu d’un char*
. L’attribut MarshalAsAttribute est utilisé pour indiquer comment la chaîne managée doit être marshalée quand elle TakesAString
est appelée.
// MarshalString.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
value struct TraditionalDLL
{
[DllImport("TraditionalDLL2.dll")]
static public void
TakesAString([MarshalAs(UnmanagedType::LPStr)]String^);
};
int main() {
String^ s = gcnew String("sample string");
Console::WriteLine("[managed] passing managed string to unmanaged function...");
TraditionalDLL::TakesAString(s);
Console::WriteLine("[managed] {0}", s);
}
Cette technique construit une copie de la chaîne sur le tas non managé, de sorte que les modifications apportées à la chaîne par la fonction native ne seront pas reflétées dans la copie gérée de la chaîne.
Aucune partie de la DLL n’est exposée au code managé par la directive traditionnelle #include
. En fait, la DLL est accessible au moment de l’exécution uniquement, de sorte que les problèmes dans les fonctions importées à l’aide DllImport
ne sont pas détectés au moment de la compilation.
Voir aussi
Utilisation de P/Invoke explicite en C++ (DllImport
attribut)