Поделиться через


Практическое руководство. Маршалирование строк с помощью P/Invoke

Собственные функции, принимаюющие строки в стиле C, можно вызывать с помощью типа System::String строки CLR с помощью поддержки платформа .NET Framework Platform Invoke (P/Invoke). Мы рекомендуем использовать функции взаимодействия C++ вместо P/Invoke, когда это возможно. Поскольку P/Invoke предоставляет мало отчетов об ошибках во время компиляции, не является типобезопасным и может быть небезопасным для реализации. Если неуправляемый API упакован в виде библиотеки DLL, и исходный код недоступен, P/Invoke является единственным вариантом. В противном случае см. раздел "Использование взаимодействия C++ (неявное P/Invoke)".

Управляемые и неуправляемые строки размещаются по-разному в памяти, поэтому передача строк из управляемых в неуправляемые функции требует MarshalAsAttribute от атрибута указать компилятору вставлять необходимые механизмы преобразования для маршалинга строковых данных правильно и безопасно.

Как и функции, использующие только встроенные типы данных, DllImportAttribute используются для объявления управляемых точек входа в собственные функции. Функции, которые передают строки, могут использовать дескриптор для String типа вместо определения этих точек входа при использовании строк в стиле C. При использовании этого типа компилятор запрашивает вставку кода, выполняющего необходимое преобразование. Для каждого аргумента функции в неуправляемой функции, принимающей строку, используйте MarshalAsAttribute атрибут, чтобы указать, что String объект должен быть маршалирован в собственную функцию в виде строки в стиле C.

Маршалер упаковывает вызов неуправляемой функции в скрытую подпрограмму оболочки. Подпрограмма-оболочка закрепляет и копирует управляемую строку в локально выделенной строке в неуправляемом контексте. Затем локальная копия передается неуправляемой функции. Когда неуправляемая функция возвращается, оболочка удаляет ресурс. Или, если он был на стеке, он был восстановлен, когда оболочка выходит из область. Неуправляемая функция не отвечает за эту память. Неуправляемый код создает и удаляет память только в куче, настроенной собственной CRT, поэтому никогда не возникает проблемы с маршаллером с использованием другой версии CRT.

Если неуправляемая функция возвращает строку в виде возвращаемого значения или параметра out, маршалер копирует его в новую управляемую строку, а затем освобождает память. Дополнительные сведения см. в разделе "Поведение маршалинга по умолчанию" и "Маршалинг данных" с помощью вызова платформы.

Пример

Следующий код состоит из неуправляемого модуля и управляемого модуля. Неуправляемый модуль — это библиотека DLL, которая определяет функцию, называемую TakesAString. TakesAStringпринимает узкую строку в стиле C в виде .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);
}

Управляемый модуль — это приложение командной строки, которое импортирует TakesAString функцию, но определяет ее как управляемую System.String вместо .char* Атрибут MarshalAsAttribute используется для указания того, как следует маршалировать управляемую строку при TakesAString вызове.

// 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 не обнаруживаются во время компиляции.

См. также

Использование явного P/Invoke в C++ (DllImport атрибут)