Практическое руководство. Маршалирование строк с помощью 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)