Compartilhar via


Como realizar marshaling de cadeias de caracteres usando PInvoke

Funções nativas que aceitem cadeias de caracteres no estilo C podem ser chamadas usando o tipo System::String de cadeia de caracteres CLR ao usar o suporte para invocação de plataforma (P/Invoke) do .NET Framework. Incentivamos você a usar os recursos de interoperabilidade do C++, em vez de P/Invoke, quando possível. Como o P/Invoke fornece pouco relatório de erros em tempo de compilação, não é fortemente tipado e a implementação dele pode ser entediante. Se a API não gerenciada for empacotada como uma DLL e o código-fonte não estiver disponível, então P/Invoke será a única opção. Caso contrário, confira Usando a interoperabilidade com C++ (P/Invoke implícito).

Cadeias de caracteres gerenciadas e não gerenciadas são dispostas de forma diferente na memória, portanto, passar cadeias de caracteres de funções gerenciadas para não gerenciadas requer o atributo MarshalAsAttribute para instruir o compilador a inserir os mecanismos de conversão necessários para marshaling dos dados de cadeia de caracteres de forma correta e segura.

Assim como acontece com funções que usam apenas tipos de dados intrínsecos, DllImportAttribute é usado para declarar pontos de entrada gerenciados nas funções nativas. As funções que passam cadeias de caracteres podem usar um identificador para o tipo String em vez de definir esses pontos de entrada como usando cadeias de caracteres no estilo C. O uso desse tipo solicita que o compilador insira o código que executa a conversão necessária. Para cada argumento de função em uma função não gerenciada que usa uma cadeia de caracteres, use o atributo MarshalAsAttribute para indicar que deve ser realizado marshaling no objeto String para a função nativa como uma cadeia de caracteres de estilo C.

O marshaler encapsula a chamada para a função não gerenciada em uma rotina de wrapper oculta. A rotina de wrapper fixa e copia a cadeia de caracteres gerenciada em uma cadeia de caracteres alocada localmente no contexto não gerenciado. A cópia local é então passada para a função não gerenciada. Quando a função não gerenciada retorna, o wrapper exclui o recurso. Ou, se estava na pilha, é recuperado quando o wrapper sai do escopo. A função não gerenciada não é responsável por essa memória. O código não gerenciado só cria e exclui a memória no heap configurado por seu próprio CRT, portanto, nunca há um problema com o marshaller usando uma versão diferente do CRT.

Se sua função não gerenciada retornar uma cadeia de caracteres, como um valor retornado ou um parâmetro out, o marshaler a copiará em uma nova cadeia de caracteres gerenciada e, em seguida, liberará a memória. Para obter mais informações, consulte Comportamento de marshaling padrão e Marshaling de dados com a invocação de plataforma.

Exemplo

O código a seguir consiste em um módulo não gerenciado e um módulo gerenciado. O módulo não gerenciado é uma DLL que define uma função chamada TakesAString. TakesAString aceita uma cadeia de caracteres estreita no estilo C na forma de um 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);
}

O módulo gerenciado é um aplicativo de linha de comando que importa a função TakesAString, mas define-a como usando um System.String gerenciado em vez de um char*. O atributo MarshalAsAttribute é usado para indicar como deve ser realizado marshalling na cadeia de caracteres gerenciada quando TakesAString é chamado.

// 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);
}

Essa técnica constrói uma cópia da cadeia de caracteres no heap não gerenciado, portanto, as alterações feitas na cadeia de caracteres pela função nativa não serão refletidas na cópia gerenciada da cadeia de caracteres.

Nenhuma parte da DLL é exposta ao código gerenciado usando a diretiva #include tradicional. Na verdade, a DLL é acessada somente em tempo de execução, portanto, problemas em funções importadas usando DllImport não são detectados em tempo de compilação.

Confira também

Usando P/Invoke explícito no C++ (atributo DllImport)