Udostępnij przez


Domyślne przekształcanie ciągów znaków

System.String i System.Text.StringBuilder Obie klasy mają podobne zachowanie marshallingowe.

Ciągi są przekazywane jako typ stylu COM BSTR lub jako ciąg zakończony znakiem o wartości null (tablica znaków kończąca się znakiem o wartości null). Znaki w ciągu mogą być skonwertowane jako Unicode (domyślnie w systemach Windows) lub ANSI.

Ciągi używane w interfejsach

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

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

Ta tabela ma zastosowanie do String. W przypadku StringBuilder, 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łaniach platformy

Gdy CharSet jest Unicode lub argument ciągu znaków jest jawnie oznaczony jako [MarshalAs(UnmanagedType.LPWSTR)] i ciąg przekazywany jest przez wartość (nie ref ani out), ciąg jest przypięty i używany bezpośrednio przez kod natywny. W przeciwnym razie wywołanie platformy kopiuje argumenty ciągów, konwertując je z formatu .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 znakowy jest przekazywany przez referencję i przydzielana jest nowa 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 łańcuchów jako argumentów metody wywołania funkcji w ramach wywołania platformy. Atrybut MarshalAsAttribute zawiera kilka UnmanagedType wartości wyliczenia do przekazywania ciągów.

Typ wyliczenia Opis formatu niezarządzanego
UnmanagedType.AnsiBStr Styl COM BSTR o poprzedzonej długości i znakach ANSI.
UnmanagedType.BStr Styl COM BSTR z prefiksowaną długością i znakami Unicode.
UnmanagedType.LPStr (ustawienie domyślne) Wskaźnik do tablicy znaków ANSI zakończonej znakiem null.
UnmanagedType.LPTStr Wskaźnik do tablicy znaków zależnych od platformy zakończonej znakiem null.
UnmanagedType.LPUTF8Str Wskaźnik do tablicy znaków zakodowanej w formacie UTF-8, zakończonej wartością null.
UnmanagedType.LPWStr Wskaźnik do tablicy znaków Unicode zakończonych wartościami null.
UnmanagedType.TBStr Styl BSTR COM z długością poprzedzoną prefiksem oraz 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 StringBuilder jedynymi dozwolonymi opcjami są LPStr, LPTStr i LPWStr.

Poniższa definicja typu przedstawia poprawne użycie MarshalAsAttribute 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 członkami struktur; jednak bufory StringBuilder są nieprawidłowe w strukturach. W poniższej tabeli przedstawiono opcje serializacji dla typu danych String, gdy typ jest serializowany jako pole. Atrybut MarshalAsAttribute udostępnia kilka UnmanagedType wartości wyliczenia do przekazywania ciągów znaków do pola.

Typ wyliczenia Opis formatu niezarządzanego
UnmanagedType.BStr Styl COM BSTR z prefiksowaną długością i znakami Unicode.
UnmanagedType.LPStr (ustawienie domyślne) Wskaźnik do tablicy znaków ANSI zakończonej znakiem null.
UnmanagedType.LPTStr Wskaźnik do tablicy znaków zależnych od platformy zakończonej znakiem null.
UnmanagedType.LPUTF8Str Wskaźnik do tablicy znaków zakodowanej w formacie UTF-8, zakończonej 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 struktury zawierającej.

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

Argument CharSet, który jest stosowany do struktury zawierającej StructLayoutAttribute, określa format znaków ciągów w tych strukturach. Poniższe przykładowe struktury zawierają odwołania do ciągów, ciągi w tekście, a także znaki ANSI, Unicode oraz zależne od 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ż odbiorca nie może modyfikować zawartości przekazanego buforu. Nawet gdy ciąg jest przekazywany jako referencja, nie ma sposobu na zainicjowanie buforu do określonego rozmiaru.

Rozwiązaniem jest przekazanie elementu byte[] lub char[] jako argumentu, w zależności od oczekiwanego kodowania, zamiast String. 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. Wywołujący ma przydzielić bufor i ustawić nMaxCount argument na rozmiar przydzielonego bufora. 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łuszczony i zmodyfikowany przez funkcję wywołującą. W poniższym przykładzie kodu pokazano, jak można użyć ArrayPool<char> do wstępnego przydzielenia 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 jako argumentu zamiast String. Bufor utworzony podczas marshalingu StringBuilder może być dereferencjonowany i zmodyfikowany przez obiekt wywoływany, o ile nie przekracza pojemności StringBuilder. 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 / Notatka

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 także