Så här gör du: Marshal-strängar med P/Invoke

Inbyggda funktioner som accepterar strängar i C-stil kan anropas med hjälp av CLR-strängtypen System::String med hjälp av stöd för .NET Framework Platform Invoke (P/Invoke). Vi rekommenderar att du använder C++ Interop-funktionerna i stället för P/Invoke när det är möjligt. eftersom P/Invoke ger lite kompileringstidsfelrapportering, inte är typsäker och kan vara omständlig att implementera. Om det ohanterade API:et paketeras som en DLL och källkoden inte är tillgänglig är P/Invoke det enda alternativet. I annat fall kan du läsa Använda C++ Interop (implicit P/Invoke).

Hanterade och ohanterade strängar anges på olika sätt i minnet, så om du skickar strängar från hanterade till ohanterade funktioner krävs MarshalAsAttribute attributet för att instruera kompilatorn att infoga de konverteringsmekanismer som krävs för att konvertera strängdata korrekt och säkert.

Precis som med funktioner som endast använder inbyggda datatyper DllImportAttribute används för att deklarera hanterade startpunkter i de inbyggda funktionerna. Funktioner som skickar strängar kan använda ett handtag till String-typen istället för att definiera dessa som tar strängar i C-stil. Med den här typen uppmanas kompilatorn att infoga kod som utför den nödvändiga konverteringen. För varje funktionsargument i en ohanterad funktion som tar en sträng använder du MarshalAsAttribute attributet för att ange att String objektet ska konverteras till den interna funktionen som en C-sträng.

Marshaler omsluter anropet till den ohanterade funktionen i en gömd omslagsrutin. Omslutningsrutinen fäster och kopierar den hanterade strängen till en lokalt allokerad sträng i den ohanterade kontexten. Den lokala kopian skickas sedan till den ohanterade funktionen. När den ohanterade funktionen returnerar tar omslutningen bort resursen. Eller, om den fanns på stacken, frigörs den när omslutningen hamnar utanför omfånget. Den ohanterade funktionen ansvarar inte för det här minnet. Ohanterad kod skapar och tar bara bort minne i heapen som har konfigurerats av sin egen CRT, så det är aldrig något problem med marshaller som använder en annan CRT-version.

Om den ohanterade funktionen returnerar en sträng, antingen som ett returvärde eller en out-parameter, kopierar marshallen den till en ny hanterad sträng och frigör sedan minnet. Mer information finns i Default Marshaling Behavior och Marshaling Data with Platform Invoke (Standardhanteringsbeteende och marskalkering av data med plattformsanrop).

Exempel

Följande kod består av en ohanterad modul och en hanterad modul. Den ohanterade modulen är en DLL som definierar en funktion med namnet TakesAString. TakesAString accepterar en smal sträng i C-format i form av en 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);
}

Den hanterade modulen är ett kommandoradsprogram som importerar funktionen TakesAString, men definierar den så att den tar en hanterad System.String istället för en char*. Attributet MarshalAsAttribute används för att ange hur den hanterade strängen ska konverteras när TakesAString anropas.

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

Den här tekniken konstruerar en kopia av strängen på den ohanterade högen, så ändringar som gjorts i strängen av den inbyggda funktionen återspeglas inte i den hanterade kopian av strängen.

Ingen del av DLL:n exponeras för den hanterade koden i det traditionella #include direktivet. I själva verket används DLL endast under körning, så problem i funktioner som importeras med hjälp av DllImport identifieras inte vid kompileringstillfället.

Se även

Använda explicit P/Invoke i C++ (DllImport attribut)