Sdílet prostřednictvím


Výchozí zařazování řetězců

Oba System.String a System.Text.StringBuilder mají podobné marshaling chování.

Řetězce jsou zařazovány jako typ stylu COM nebo jako řetězec ukončený nulovým znakem (pole znaků končící nulovým znakem). Znaky v řetězci mohou být zařazovány jako Unicode (výchozí nastavení v systémech Windows) nebo ANSI.

Řetězce používané v rozhraních

Následující tabulka ukazuje možnosti zařazování pro datový typ řetězce při zařazování jako argument metody pro nespravovaný kód. Atribut MarshalAsAttribute poskytuje několik UnmanagedType hodnot výčtu pro předávání řetězců do rozhraní COM.

Typ výčtu Popis neřízeného formátu
UnmanagedType.BStr (výchozí) Styl ve stylu COM BSTR s prefixovou délkou a znaky Unicode.
UnmanagedType.LPStr Ukazatel na pole znaků ANSI, které je ukončeno nulovým znakem.
UnmanagedType.LPWStr Ukazatel na pole znaků Unicode, které je ukončeno nulovým znakem.

Tato tabulka platí pro String. Pro StringBuilder, jediné povolené možnosti jsou UnmanagedType.LPStr a UnmanagedType.LPWStr.

Následující příklad ukazuje řetězce deklarované v IStringWorker rozhraní.

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

Následující příklad ukazuje odpovídající rozhraní popsané v knihovně typů.

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

Řetězce používané při vyvolání funkcí platformy

Pokud je CharSet Unicode nebo je řetězcový argument explicitně označen jako [MarshalAs(UnmanagedType.LPWSTR)], a řetězec je předán hodnotou (ne ref nebo out), řetězec je připoután a použit přímo nativním kódem. V opačném případě platforma vyvolá kopie řetězcových argumentů, převede z formátu .NET Framework (Unicode) na nespravovaný formát platformy. Řetězce jsou neměnné a při návratu volání se nekopírují zpět z nespravované paměti do paměti spravované.

Nativní kód je zodpovědný pouze za uvolnění paměti, pokud je proměnná předána odkazem a je jí přiřazena nová hodnota. V opačném případě modul runtime .NET vlastní paměť a uvolní ji po volání.

V následující tabulce jsou uvedeny možnosti marshalování řetězců při marshalování jako argumentu metody platformního vyvolání. Atribut MarshalAsAttribute poskytuje několik UnmanagedType hodnot výčtu pro zařazování řetězců.

Typ výčtu Popis neřízeného formátu
UnmanagedType.AnsiBStr Styl BSTR modelu COM s délkou s předponou a znaky ANSI.
UnmanagedType.BStr Styl ve stylu COM BSTR s prefixovou délkou a znaky Unicode.
UnmanagedType.LPStr (výchozí) Ukazatel na pole znaků ANSI, které je ukončeno nulovým znakem.
UnmanagedType.LPTStr Ukazatel na pole ukončené nulovým znakem obsahující znaky závislé na platformě.
UnmanagedType.LPUTF8Str Ukazatel na pole znaků kódovaných v UTF-8, ukončeného nulovým znakem.
UnmanagedType.LPWStr Ukazatel na pole znaků Unicode, které je ukončeno nulovým znakem.
UnmanagedType.TBStr Styl BSTR COM s předponovou délkou a znaky závislými na platformě.
VBByRefStr Hodnota, která umožňuje jazyku Visual Basic změnit řetězec v nespravovaném kódu a výsledky se projeví ve spravovaném kódu. Tato hodnota je podporována pouze pro volání platformy. Toto je výchozí hodnota jazyka Visual Basic pro ByVal řetězce.

Tato tabulka platí pro String. Pro StringBuilder, jediné povolené možnosti jsou LPStr, LPTStra LPWStr.

Následující definice typu ukazuje správné použití MarshalAsAttribute při volání 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

Řetězce používané ve strukturách

Řetězce jsou platnými složkami datových struktur; vyrovnávací paměti StringBuilder jsou však v datových strukturách neplatné. Následující tabulka ukazuje možnosti seřazování datového String typu, když je typ zařazován jako pole. Atribut MarshalAsAttribute poskytuje několik UnmanagedType hodnot výčtu pro zařazování řetězců do pole.

Typ výčtu Popis neřízeného formátu
UnmanagedType.BStr Styl ve stylu COM BSTR s prefixovou délkou a znaky Unicode.
UnmanagedType.LPStr (výchozí) Ukazatel na pole znaků ANSI, které je ukončeno nulovým znakem.
UnmanagedType.LPTStr Ukazatel na pole ukončené nulovým znakem obsahující znaky závislé na platformě.
UnmanagedType.LPUTF8Str Ukazatel na pole znaků kódovaných v UTF-8, ukončeného nulovým znakem.
UnmanagedType.LPWStr Ukazatel na pole znaků Unicode, které je ukončeno nulovým znakem.
UnmanagedType.ByValTStr Pole znaků s pevnou délkou; typ pole je určen znakovou sadou obsahující struktury.

Typ ByValTStr se používá pro vložená pole znaků s pevnou délkou, která se zobrazují ve struktuře. Jiné typy se vztahují na odkazy na řetězce obsažené ve strukturách, které obsahují ukazatele na řetězce.

Argument CharSet použitý u struktury, která obsahuje strukturu StructLayoutAttribute, určuje znakový formát řetězců ve strukturách. Následující ukázkové struktury obsahují odkazy na řetězce a vložené řetězce a také znaky závislé na standardu ANSI, Unicode a platformě. Reprezentace těchto struktur v knihovně typů je znázorněna v následujícím kódu jazyka C++:

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

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

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

Následující příklad ukazuje, jak použít MarshalAsAttribute k definování stejné struktury v různých formátech.

[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

Řetězcové vyrovnávací paměti s pevnou délkou

V některých případech musí být vyrovnávací paměť znaků s pevnou délkou předána do nespravovaného kódu, aby s ním bylo možné manipulovat. Jednoduše předání řetězce v tomto případě nefunguje, protože volaný nemůže změnit obsah předaného pufru. I když je řetězec předán odkazem, neexistuje způsob, jak inicializovat vyrovnávací paměť na danou velikost.

Řešením je předat byte[] nebo char[], v závislosti na očekávaném kódování, jako argument místo String. Pole, pokud je označeno značkou [Out], může být dereferenceováno a upraveno volajícím, pokud nepřekračuje kapacitu přiděleného pole.

Například funkce rozhraní API systému Windows GetWindowText (definovaná v winuser.h) vyžaduje, aby volající musí předat znakový buffer s pevnou délkou, do kterého funkce zapisuje text okna. Argument lpString odkazuje na vyrovnávací paměť o velikosti nMaxCount, kterou přidělil volající. Očekává se, že volající přidělí vyrovnávací paměť a nastaví nMaxCount argument na velikost přidělené vyrovnávací paměti. Následující příklad ukazuje deklaraci funkce definovanou GetWindowText v winuser.h.

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

A char[] může být dereferencován a upraven volajícím. Následující příklad kódu ukazuje, jak ArrayPool<char> lze použít k předběžnému přidělení 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

Dalším řešením je předat StringBuilder jako argument místo argumentu String. Vyrovnávací paměť vytvořená při zařazování StringBuilder může být dereferencována a upravena volajícím, za předpokladu, že nepřekračuje kapacitu StringBuilder. Dá se také inicializovat na pevnou délku. Pokud například inicializujete StringBuilder vyrovnávací paměť s kapacitou N, marshaller poskytuje vyrovnávací paměť o velikosti N+1 znaků. +1 představuje skutečnost, že nespravovaný řetězec má ukončovací znak null, zatímco StringBuilder ne.

Poznámka:

Obecně platí, že předávání StringBuilder argumentů se nedoporučuje, pokud máte obavy o výkon. Další informace naleznete v tématu Parametry řetězce.

Viz také