Instrukcje: przeprowadzanie marshalingu ciągów przy użyciu funkcji P/Invoke
Funkcje natywne, które akceptują ciągi w stylu C, mogą być wywoływane przy użyciu typu System::String
ciągu CLR przy użyciu obsługi wywołania platformy .NET Framework (P/Invoke). Zachęcamy do korzystania z funkcji międzyoperacyjności języka C++ zamiast funkcji P/Invoke, jeśli jest to możliwe. Ponieważ funkcja P/Invoke zapewnia niewielkie raportowanie błędów w czasie kompilacji, nie jest bezpieczne dla typu i nie może być żmudne do zaimplementowania. Jeśli niezarządzany interfejs API jest spakowany jako biblioteka DLL, a kod źródłowy nie jest dostępny, P/Invoke jest jedyną opcją. W przeciwnym razie zobacz Używanie międzyoperacyjności języka C++ (niejawne wywołanie P/Invoke).
Zarządzane i niezarządzane ciągi są rozmieszczone inaczej w pamięci, dlatego przekazywanie ciągów z zarządzanych do niezarządzanych funkcji wymaga MarshalAsAttribute , aby kompilator wstawić wymagane mechanizmy konwersji do poprawnego i bezpiecznego marshalingu danych ciągów.
Podobnie jak w przypadku funkcji korzystających tylko z wewnętrznych typów danych, DllImportAttribute służy do deklarowania zarządzanych punktów wejścia do funkcji natywnych. Funkcje, które przekazują ciągi, mogą używać uchwytu do String typu zamiast definiowania tych punktów wejścia jako przyjmowania ciągów w stylu C. Użycie tego typu powoduje wyświetlenie monitu kompilatora o wstawienie kodu wykonującego wymaganą konwersję. Dla każdego argumentu funkcji w funkcji niezarządzanej, która przyjmuje ciąg, użyj atrybutu MarshalAsAttribute , aby wskazać, że String
obiekt powinien być marshaled do funkcji natywnej jako ciąg w stylu C.
Marshaler owija wywołanie funkcji niezarządzanej w ukrytej procedurze otoki. Procedury otoki przypiąć i skopiować zarządzany ciąg do lokalnie przydzielonego ciągu w kontekście niezarządzanym. Kopia lokalna jest następnie przekazywana do funkcji niezarządzanej. Gdy funkcja niezarządzana zwróci, otoka usuwa zasób. Lub, jeśli był na stosie, jest odzyskiwane, gdy otoka wykracza poza zakres. Niezarządzana funkcja nie jest odpowiedzialna za tę pamięć. Niezarządzany kod tworzy i usuwa tylko pamięć w stercie skonfigurowanej przez własny CRT, więc nigdy nie występuje problem z marshallerem przy użyciu innej wersji CRT.
Jeśli funkcja niezarządzana zwraca ciąg, jako wartość zwracaną lub parametr wyjściowy, marshaler kopiuje go do nowego zarządzanego ciągu, a następnie zwalnia pamięć. Aby uzyskać więcej informacji, zobacz Domyślne działanie marshalingu i marshaling danych za pomocą wywołania platformy.
Przykład
Poniższy kod składa się z niezarządzanego modułu i zarządzanego modułu. Niezarządzany moduł to biblioteka DLL, która definiuje funkcję o nazwie TakesAString
. TakesAString
akceptuje wąski ciąg w stylu C w postaci .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);
}
Moduł zarządzany to aplikacja wiersza polecenia, która importuje TakesAString
funkcję, ale definiuje ją jako zarządzaną System.String
zamiast char*
. Atrybut MarshalAsAttribute służy do wskazywania sposobu, w jaki ciąg zarządzany powinien być marshalowany, gdy TakesAString
jest wywoływany.
// 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);
}
Ta technika tworzy kopię ciągu na stosie niezarządzanym, więc zmiany wprowadzone w ciągu przez funkcję natywną nie zostaną odzwierciedlone w zarządzanej kopii ciągu.
Żadna część biblioteki DLL nie jest widoczna dla kodu zarządzanego przez tradycyjną #include
dyrektywę. W rzeczywistości biblioteka DLL jest dostępna tylko w czasie wykonywania, więc problemy z funkcjami importowanymi przy użyciu DllImport
nie są wykrywane w czasie kompilacji.