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

В этом разделе описывается, как можно вызывать неуправляемые функции, принимающие строки, характерные для языка 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)