Domyślne marshaling dla ciągów

System.String Oba klasy i System.Text.StringBuilder mają podobne zachowanie marshallingu.

Ciągi są uściśliwane jako typ stylu BSTR COM lub jako ciąg zakończony wartością null (tablica znaków kończąca się znakiem null). Znaki w ciągu można ustawić jako Unicode (wartość domyślna w systemach Windows) lub ANSI.

Ciągi używane w interfejsach

W poniższej tabeli przedstawiono opcje marshalingu dla typu danych ciągu, gdy jest rozdzielany jako argument metody do niezarządzanego kodu. Atrybut MarshalAsAttribute udostępnia kilka UnmanagedType wartości wyliczenia do marshalingu ciągów do interfejsów COM.

Typ wyliczenia Opis formatu niezarządzanego
UnmanagedType.BStr (domyślne) Styl BSTR COM z poprzedzoną długością i znakami Unicode.
UnmanagedType.LPStr Wskaźnik do tablicy zakończonej wartością null znaków ANSI.
UnmanagedType.LPWStr Wskaźnik do tablicy znaków Unicode zakończonych wartościami null.

Ta tabela ma zastosowanie do String. W przypadku StringBuilderprogramu jedynymi dozwolonymi opcjami są UnmanagedType.LPStr i UnmanagedType.LPWStr.

W poniższym przykładzie pokazano ciągi zadeklarowane w interfejsie IStringWorker .

public interface IStringWorker
{
    void PassString1(string s);
    void PassString2([MarshalAs(UnmanagedType.BStr)] string s);
    void PassString3([MarshalAs(UnmanagedType.LPStr)] string s);
    void PassString4([MarshalAs(UnmanagedType.LPWStr)] string s);
    void PassStringRef1(ref string s);
    void PassStringRef2([MarshalAs(UnmanagedType.BStr)] ref string s);
    void PassStringRef3([MarshalAs(UnmanagedType.LPStr)] ref string s);
    void PassStringRef4([MarshalAs(UnmanagedType.LPWStr)] ref string s);
}
Public Interface IStringWorker
    Sub PassString1(s As String)
    Sub PassString2(<MarshalAs(UnmanagedType.BStr)> s As String)
    Sub PassString3(<MarshalAs(UnmanagedType.LPStr)> s As String)
    Sub PassString4(<MarshalAs(UnmanagedType.LPWStr)> s As String)
    Sub PassStringRef1(ByRef s As String)
    Sub PassStringRef2(<MarshalAs(UnmanagedType.BStr)> ByRef s As String)
    Sub PassStringRef3(<MarshalAs(UnmanagedType.LPStr)> ByRef s As String)
    Sub PassStringRef4(<MarshalAs(UnmanagedType.LPWStr)> ByRef s As String)
End Interface

W poniższym przykładzie pokazano odpowiedni interfejs opisany w bibliotece typów.

interface IStringWorker : IDispatch
{
    HRESULT PassString1([in] BSTR s);
    HRESULT PassString2([in] BSTR s);
    HRESULT PassString3([in] LPStr s);
    HRESULT PassString4([in] LPWStr s);
    HRESULT PassStringRef1([in, out] BSTR *s);
    HRESULT PassStringRef2([in, out] BSTR *s);
    HRESULT PassStringRef3([in, out] LPStr *s);
    HRESULT PassStringRef4([in, out] LPWStr *s);
};

Ciągi używane w wywołaniu platformy

Gdy element CharSet to Unicode lub argument ciągu jest jawnie oznaczony jako [MarshalAs(UnmanagedType.LPWSTR)] i ciąg jest przekazywany przez wartość (nie ref lub out), ciąg jest przypięty i używany bezpośrednio przez kod macierzysty. W przeciwnym razie platforma wywołuje argumenty ciągu kopii, konwertując format programu .NET Framework (Unicode) na format niezarządzany platformy. Ciągi są niezmienne i nie są kopiowane z niezarządzanej pamięci do pamięci zarządzanej po powrocie wywołania.

Kod natywny jest odpowiedzialny tylko za zwolnienie pamięci, gdy ciąg jest przekazywany przez odwołanie i przypisuje nową wartość. W przeciwnym razie środowisko uruchomieniowe platformy .NET jest właścicielem pamięci i zwolni je po wywołaniu.

W poniższej tabeli wymieniono opcje marshalingu dla ciągów, gdy są uruchamiane jako argument metody wywołania wywołania platformy. Atrybut MarshalAsAttribute zawiera kilka UnmanagedType wartości wyliczenia do marshalingu ciągów.

Typ wyliczenia Opis formatu niezarządzanego
UnmanagedType.AnsiBStr Styl BSTR MODELU COM z poprzedzoną długością i znakami ANSI.
UnmanagedType.BStr Styl BSTR COM z poprzedzoną długością i znakami Unicode.
UnmanagedType.LPStr (domyślne) Wskaźnik do tablicy zakończonej wartością null znaków ANSI.
UnmanagedType.LPTStr Wskaźnik do tablicy znaków zależnych od platformy o wartości null.
UnmanagedType.LPUTF8Str Wskaźnik do tablicy zakodowanej w formacie UTF-8 o wartości null.
UnmanagedType.LPWStr Wskaźnik do tablicy znaków Unicode zakończonych wartościami null.
UnmanagedType.TBStr Styl BSTR MODELU COM z poprzedzoną długością i znakami zależnymi od platformy.
VBByRefStr Wartość umożliwiająca programowi Visual Basic zmianę ciągu w kodzie niezarządzanym i odzwierciedlanie wyników w kodzie zarządzanym. Ta wartość jest obsługiwana tylko w przypadku wywołania platformy. Jest to wartość domyślna w języku Visual Basic dla ByVal ciągów.

Ta tabela ma zastosowanie do String. W przypadku StringBuilderprogramu jedyną dozwoloną opcją są LPStr, LPTStri LPWStr.

Poniższa definicja typu przedstawia poprawne użycie MarshalAsAttribute wywołań wywołań platformy.

class StringLibAPI
{
    [DllImport("StringLib.dll")]
    public static extern void PassLPStr([MarshalAs(UnmanagedType.LPStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassLPWStr([MarshalAs(UnmanagedType.LPWStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassLPTStr([MarshalAs(UnmanagedType.LPTStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassLPUTF8Str([MarshalAs(UnmanagedType.LPUTF8Str)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassBStr([MarshalAs(UnmanagedType.BStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassAnsiBStr([MarshalAs(UnmanagedType.AnsiBStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassTBStr([MarshalAs(UnmanagedType.TBStr)] string s);
}
Class StringLibAPI
    Public Declare Auto Sub PassLPStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPStr)> s As String)
    Public Declare Auto Sub PassLPWStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPWStr)> s As String)
    Public Declare Auto Sub PassLPTStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPTStr)> s As String)
    Public Declare Auto Sub PassLPUTF8Str Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPUTF8Str)> s As String)
    Public Declare Auto Sub PassBStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.BStr)> s As String)
    Public Declare Auto Sub PassAnsiBStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.AnsiBStr)> s As String)
    Public Declare Auto Sub PassTBStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.TBStr)> s As String)
End Class

Ciągi używane w strukturach

Ciągi są prawidłowymi elementami członkowskimi struktur; bufory są jednak StringBuilder nieprawidłowe w strukturach. W poniższej tabeli przedstawiono opcje marshalingu String dla typu danych, gdy typ jest uśmiercony jako pole. Atrybut MarshalAsAttribute udostępnia kilka UnmanagedType wartości wyliczenia do marshalingu ciągów do pola.

Typ wyliczenia Opis formatu niezarządzanego
UnmanagedType.BStr Styl BSTR COM z poprzedzoną długością i znakami Unicode.
UnmanagedType.LPStr (domyślne) Wskaźnik do tablicy zakończonej wartością null znaków ANSI.
UnmanagedType.LPTStr Wskaźnik do tablicy znaków zależnych od platformy o wartości null.
UnmanagedType.LPUTF8Str Wskaźnik do tablicy zakodowanej w formacie UTF-8 o wartości null.
UnmanagedType.LPWStr Wskaźnik do tablicy znaków Unicode zakończonych wartościami null.
UnmanagedType.ByValTStr Tablica znaków o stałej długości; typ tablicy jest określany przez zestaw znaków zawierającej strukturę.

Typ ByValTStr jest używany dla wbudowanych tablic znaków o stałej długości, które pojawiają się w strukturze. Inne typy mają zastosowanie do odwołań ciągów zawartych w strukturach zawierających wskaźniki do ciągów.

CharSet Argument StructLayoutAttribute zastosowany do struktury zawierającej określa format znaków ciągów w strukturach. Poniższe przykładowe struktury zawierają odwołania do ciągów i ciągi śródwierszowe, a także znaki zależne od anSI, Unicode i platformy. Reprezentacja tych struktur w bibliotece typów jest wyświetlana w następującym kodzie języka C++:

struct StringInfoA
{
    char *  f1;
    char    f2[256];
};

struct StringInfoW
{
    WCHAR * f1;
    WCHAR   f2[256];
    BSTR    f3;
};

struct StringInfoT
{
    TCHAR * f1;
    TCHAR   f2[256];
};

W poniższym przykładzie pokazano, jak użyć obiektu MarshalAsAttribute , aby zdefiniować tę samą strukturę w różnych formatach.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct StringInfoA
{
    [MarshalAs(UnmanagedType.LPStr)] public string f1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct StringInfoW
{
    [MarshalAs(UnmanagedType.LPWStr)] public string f1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
    [MarshalAs(UnmanagedType.BStr)] public string f3;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct StringInfoT
{
    [MarshalAs(UnmanagedType.LPTStr)] public string f1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
}
<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Ansi)> _
Structure StringInfoA
    <MarshalAs(UnmanagedType.LPStr)> Public f1 As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
    Public f2 As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Unicode)> _
Structure StringInfoW
    <MarshalAs(UnmanagedType.LPWStr)> Public f1 As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
    Public f2 As String
<MarshalAs(UnmanagedType.BStr)> Public f3 As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Auto)> _
Structure StringInfoT
    <MarshalAs(UnmanagedType.LPTStr)> Public f1 As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
    Public f2 As String
End Structure

Bufory ciągów o stałej długości

W niektórych okolicznościach bufor znaków o stałej długości musi zostać przekazany do niezarządzanego kodu, aby można było manipulować nim. Po prostu przekazanie ciągu nie działa w tym przypadku, ponieważ obiekt wywoływany nie może modyfikować zawartości przekazanego buforu. Nawet jeśli ciąg jest przekazywany przez odwołanie, nie ma możliwości zainicjowania buforu do danego rozmiaru.

Rozwiązaniem jest przekazanie elementu byte[] lub char[], w zależności od oczekiwanego kodowania, jako argumentu Stringzamiast . Tablica, gdy jest oznaczona znakiem [Out], może zostać wyłuszczona i zmodyfikowana przez obiekt wywoływany, pod warunkiem, że nie przekracza pojemności przydzielonej tablicy.

Na przykład funkcja interfejsu API systemu Windows GetWindowText (zdefiniowana w pliku winuser.h) wymaga, aby obiekt wywołujący przekazał bufor znaków o stałej długości, do którego funkcja zapisuje tekst okna. Argument lpString wskazuje bufor przydzielony przez obiekt wywołujący o rozmiarze nMaxCount. Obiekt wywołujący ma przydzielić bufor i ustawić nMaxCount argument na rozmiar przydzielonego buforu. W poniższym przykładzie pokazano deklarację funkcji zdefiniowaną GetWindowText w pliku winuser.h.

int GetWindowText(
    HWND hWnd,        // Handle to window or control.
    LPTStr lpString,  // Text buffer.
    int nMaxCount     // Maximum number of characters to copy.
);

Obiekt char[] może zostać wyłuszczone i zmodyfikowany przez obiekt wywoływany. W poniższym przykładzie kodu pokazano, jak ArrayPool<char> można użyć metody do wstępnego przydzielenia klasy char[].

using System;
using System.Buffers;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    public static extern void GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);
}

public class Window
{
    internal IntPtr h;        // Internal handle to Window.
    public string GetText()
    {
        char[] buffer = ArrayPool<char>.Shared.Rent(256 + 1);
        NativeMethods.GetWindowText(h, buffer, buffer.Length);
        return new string(buffer);
    }
}
Imports System
Imports System.Buffers
Imports System.Runtime.InteropServices

Friend Class NativeMethods
    Public Declare Auto Sub GetWindowText Lib "User32.dll" _
        (hWnd As IntPtr, <Out> lpString() As Char, nMaxCount As Integer)
End Class

Public Class Window
    Friend h As IntPtr ' Friend handle to Window.
    Public Function GetText() As String
        Dim buffer() As Char = ArrayPool(Of Char).Shared.Rent(256 + 1)
        NativeMethods.GetWindowText(h, buffer, buffer.Length)
        Return New String(buffer)
   End Function
End Class

Innym rozwiązaniem jest przekazanie StringBuilder argumentu jako argumentu Stringzamiast . Bufor utworzony podczas marshalingu StringBuilder elementu może zostać wyłuszczone i zmodyfikowane przez obiekt wywoływany, pod warunkiem że nie przekracza pojemności StringBuilderobiektu . Można go również zainicjować na stałą długość. Jeśli na przykład zainicjujesz StringBuilder bufor do pojemności N, marshaller udostępnia bufor o rozmiarze (N+1) znaków. Element +1 odpowiada faktowi, że ciąg niezarządzany ma zerowy terminator, ale StringBuilder nie.

Uwaga

Ogólnie rzecz biorąc, przekazywanie StringBuilder argumentów nie jest zalecane, jeśli martwisz się o wydajność. Aby uzyskać więcej informacji, zobacz Parametry ciągu.

Zobacz też