Compartir a través de


Serialización predeterminada para cadenas

Las clases System.String y System.Text.StringBuilder tienen un comportamiento de serialización similar.

Las referencias de las cadenas se calculan como un tipo BSTR de estilo COM o como una cadena terminada en NULL (una matriz de caracteres que termina con un carácter NULL). Los caracteres de la cadena se pueden interpretar como Unicode (el valor predeterminado en los sistemas Windows) o ANSI.

Cadenas usadas en interfaces

En la tabla siguiente se muestran las opciones de intercalación para el tipo de datos de cadena cuando se intercalan como argumento de método en código no gestionado. El atributo MarshalAsAttribute proporciona varios valores de enumeración UnmanagedType para transferir cadenas a interfaces COM.

Tipo de enumeración Descripción del formato no administrado
UnmanagedType.BStr (valor predeterminado) Estilo COM BSTR con longitud prefijada y caracteres Unicode.
UnmanagedType.LPStr Puntero a una matriz de caracteres ANSI terminada en nulo.
UnmanagedType.LPWStr Puntero a una matriz de caracteres Unicode terminada en null.

Esta tabla se aplica a String. Para StringBuilder, las únicas opciones permitidas son UnmanagedType.LPStr y UnmanagedType.LPWStr.

En el ejemplo siguiente se muestran las cadenas declaradas en la IStringWorker interfaz .

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

En el ejemplo siguiente se muestra la interfaz correspondiente descrita en una biblioteca de tipos.

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

Cadenas usadas en la plataforma de invocación

Cuando CharSet es Unicode o un argumento de cadena se marca explícitamente como [MarshalAs(UnmanagedType.LPWSTR)] y la cadena se pasa por valor (no ref ni out), el código nativo ancla la cadena y la usa directamente. De lo contrario, la invocación de plataforma copia los argumentos de cadena y convierte el formato de .NET Framework (Unicode) al formato no administrado de la plataforma. Las cadenas son inmutables y no se copian de la memoria no administrada a la memoria administrada cuando la llamada regresa.

El código nativo solo es responsable de liberar la memoria cuando la cadena se pasa por referencia y asigna un nuevo valor. De lo contrario, el entorno de ejecución de .NET posee la memoria y lo liberará después de la llamada.

En la tabla siguiente se enumeran las opciones de cálculo de referencias cuando las referencias de cadenas se calculan como un argumento de método de una llamada de invocación de plataforma. El MarshalAsAttribute atributo proporciona varios UnmanagedType valores de enumeración para gestionar cadenas.

Tipo de enumeración Descripción del formato no administrado
UnmanagedType.AnsiBStr Estilo COM BSTR de longitud prefijada y caracteres ANSI.
UnmanagedType.BStr Estilo COM BSTR con longitud prefijada y caracteres Unicode.
UnmanagedType.LPStr (valor predeterminado) Puntero a una matriz de caracteres ANSI terminada en nulo.
UnmanagedType.LPTStr Un puntero a una matriz terminada en NULL de caracteres dependientes de la plataforma.
UnmanagedType.LPUTF8Str Un puntero a una matriz terminada en NULL de caracteres codificados UTF-8.
UnmanagedType.LPWStr Puntero a una matriz de caracteres Unicode terminada en null.
UnmanagedType.TBStr Estilo COM BSTR con longitud prefijada y caracteres que dependen de la plataforma.
VBByRefStr Valor que permite a Visual Basic cambiar una cadena en código no administrado y que tenga los resultados reflejados en código administrado. Este valor solo se admite con la invocación de plataforma. Este es el valor predeterminado de Visual Basic para ByVal cadenas.

Esta tabla se aplica a String. Para StringBuilder, las únicas opciones permitidas son LPStr, LPTStry LPWStr.

La siguiente definición de tipo muestra el uso correcto de MarshalAsAttribute para las llamadas de invocación de plataforma.

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

Cadenas usadas en estructuras

Las cadenas son miembros válidos de estructuras; sin embargo, los búferes StringBuilder son inválidos en las estructuras. En la tabla siguiente se muestran las opciones de marcaje del tipo de datos String cuando el tipo se maneja como un campo. El MarshalAsAttribute atributo proporciona varios UnmanagedType valores de enumeración para convertir cadenas a un campo.

Tipo de enumeración Descripción del formato no administrado
UnmanagedType.BStr Estilo COM BSTR con longitud prefijada y caracteres Unicode.
UnmanagedType.LPStr (valor predeterminado) Puntero a una matriz de caracteres ANSI terminada en nulo.
UnmanagedType.LPTStr Un puntero a una matriz terminada en NULL de caracteres dependientes de la plataforma.
UnmanagedType.LPUTF8Str Un puntero a una matriz terminada en NULL de caracteres codificados UTF-8.
UnmanagedType.LPWStr Puntero a una matriz de caracteres Unicode terminada en null.
UnmanagedType.ByValTStr Matriz de caracteres de longitud fija; El tipo de la matriz viene determinado por el juego de caracteres de la estructura contenedora.

El ByValTStr tipo se usa para matrices de caracteres insertadas y de longitud fija que aparecen dentro de una estructura. Otros tipos se aplican a las referencias de cadena contenidas en estructuras que contienen punteros a cadenas.

El argumento CharSet de StructLayoutAttribute que se aplica a la estructura que lo contiene determina el formato de los caracteres de las cadenas en las estructuras. Las siguientes estructuras de ejemplo contienen referencias de cadena y cadenas insertadas, así como caracteres ANSI, Unicode y dependientes de la plataforma. La representación de estas estructuras en una biblioteca de tipos se muestra en el siguiente código de C++:

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

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

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

En el ejemplo siguiente se muestra cómo usar MarshalAsAttribute para definir la misma estructura en formatos diferentes.

[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

Búferes de cadenas de longitud fija

En algunas circunstancias, es necesario pasar un búfer de caracteres de longitud fija al código no gestionado para que sea manipulado. En este caso no basta con pasar una cadena porque el destinatario no puede modificar el contenido del búfer que se pasa. Incluso si la cadena se pasa por referencia, no hay forma de inicializar el búfer en un tamaño determinado.

La solución consiste en pasar un byte[] o un char[], dependiendo de la codificación esperada, como argumento en lugar de un String. El destinatario puede desreferenciar y modificar la matriz cuando se marca con [Out], siempre que no exceda la capacidad de la matriz asignada.

Por ejemplo, la función de API de Windows GetWindowText (definida en winuser.h) requiere que el autor de la llamada pase un búfer de caracteres de longitud fija al que la función escribe el texto de la ventana. El argumento lpString apunta a una memoria asignada por el llamador de tamaño nMaxCount. Se espera que el llamador asigne el búfer y establezca el argumento nMaxCount en el tamaño del búfer asignado. En el ejemplo siguiente se muestra la GetWindowText declaración de función tal como se define en winuser.h.

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

El destinatario puede desreferenciar y modificar char[]. En el ejemplo de código siguiente se muestra cómo ArrayPool<char> se puede usar para asignar previamente un 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

Otra solución consiste en pasar un StringBuilder como argumento en lugar de un String. El destinatario puede desreferenciar y modificar el búfer creado al serializar una instancia de StringBuilder, siempre que no exceda la capacidad de StringBuilder. También se puede inicializar a una longitud fija. Por ejemplo, si inicializa un búfer StringBuilder con una capacidad de N, el contador de referencias proporcionará un búfer con un tamaño de (N+ 1) caracteres. El +1 tiene en cuenta el hecho de que la cadena no administrada tiene un terminador NULO mientras StringBuilder no lo hace.

Nota:

En general, no se recomienda pasar argumentos StringBuilder si le preocupa el rendimiento. Para obtener más información, consulte Parámetros de cadena.

Consulte también