Практическое руководство. Маршалирование строк с помощью PInvoke
Обновлен: Ноябрь 2007
В этом разделе описывается, как можно вызывать неуправляемые функции, принимающие строки, характерные для языка C, с помощью строкового типа среды CLR System::String, используя поддержку вызова неуправляемого кода платформы .NET Framework. Программистам Visual C++ рекомендуется использовать возможности взаимодействия C++ (когда это возможно), так как вызов P/Invoke предоставляет мало сведений об ошибках во время компиляции, не является строго типизированным и сложен в реализации. Если неуправляемый интерфейс API упакован в виде библиотеки DLL, а исходный код недоступен, то использование P/Invoke является единственным возможным вариантом, в противном случае см. раздел Использование взаимодействия языка C++ (неявный PInvoke).
Управляемые и неуправляемые строки по-разному хранятся в памяти, поэтому для передачи строк из управляемых функций в неуправляемые требуется атрибут MarshalAsAttribute, чтобы компилятор вставлял необходимые механизмы преобразования для безопасного и корректного маршалинга строковых данных.
Как и в случае функций, использующих только встроенные типы данных, атрибут DllImportAttribute используется для объявления управляемых точек входа в неуправляемые функции, но для передачи строк вместо объявления этих точек входа, принимающих строки, характерные для языка C, можно использовать дескриптор типа String. В этом случае компилятор вставляет код, выполняющий соответствующие преобразования. Для каждого аргумента неуправляемой функции, принимающей строку, атрибут MarshalAsAttribute следует использовать для обозначения того, что объект String необходимо маршалировать в неуправляемую функцию в виде строки, характерной для языка C.
Пример
Следующий код состоит из неуправляемого и управляемого модулей. Неуправляемый модуль — это библиотека DLL, в которой определена функция TakesAString, принимающая строку ANSI, характерную для языка C, в форме типа char*. Управляемый модуль — это приложение командной строки, импортирующее функцию TakesAString, однако здесь эта функция определена как принимающая объект типа System.String, а не char*. Атрибут MarshalAsAttribute используется для обозначения того, как управляемую строку следует маршалировать при вызове функции TakesAString.
Управляемый модуль компилируется с параметром /clr. Также допускается использование параметра /clr:pure.
// 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);
}
// 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);
}
При этом в неуправляемой куче создается копия строки, поэтому изменения строки, выполненный неуправляемой функцией, не отражаются в управляемой копии строки.
Обратите внимание, что DLL недоступна управляемому код через директиву #include. Обращение к DLL идет только во время выполнения, поэтому проблемы с функциями, импортируемыми с помощью DllImport, во время компиляции не обнаруживаются.
См. также
Другие ресурсы
Использование явного вызова Pinvoke в C++ (атрибут DllImport)