Маршалирование по умолчанию для строк

Как классы System.String , так и System.Text.StringBuilder имеют аналогичное поведение маршаллинга.

Строки маршалируются как тип com-стиля BSTR или как строка, завершающаяся значением NULL (массив символов, заканчивающийся пустым символом). Символы в строке можно маршалировать как Юникод (по умолчанию в системах Windows) или ANSI.

Строки, используемые в интерфейсах

В следующей таблице показаны параметры маршалинга для типа строковых данных при маршале в качестве аргумента метода для неуправляемого кода. Атрибут MarshalAsAttribute предоставляет несколько значений перечисления UnmanagedType для маршалинга строк в COM-интерфейсы.

Тип перечисления Описание неуправляемого формата
UnmanagedType.BStr (по умолчанию) Тип BSTR стиля COM с фиксированной длиной и символами Юникода.
UnmanagedType.LPStr Указатель на массив символов в кодировке ANSI, завершающийся значением null.
UnmanagedType.LPWStr Указатель на строку знаков в кодировке Юникод, завершающуюся нулевым значением.

Эта таблица применяется к String. Для StringBuilder единственными допустимыми вариантами являются UnmanagedType.LPStr и UnmanagedType.LPWStr.

В примере ниже показаны строки, объявленные в интерфейсе 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

В примере ниже показан соответствующий интерфейс, описанный в библиотеке типов.

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

Строки, используемые в вызове платформы

Если выбрана кодировка Юникод или аргумент явно обозначен как [MarshalAs(UnmanagedType.LPWSTR)] и строка передается по значению (не ref или out), строка будет закреплена, чтобы использоваться непосредственно в машинном коде. Иначе вызов неуправляемого кода будет копировать строковые аргументы, выполняя преобразование из формата .NET Framework (Юникод) в неуправляемый формат платформы. При возврате из вызова строки не изменяются и не копируются обратно из неуправляемой памяти в управляемую.

Машинный код отвечает только за освобождение памяти, когда строка передается по ссылке, и присваивает новое значение. Иначе среда выполнения .NET будет использовать память и освобождать ее после вызова.

В следующей таблице перечислены параметры маршалинга строк при маршале в качестве аргумента метода вызова платформы. Атрибут MarshalAsAttribute предоставляет несколько значений перечисления UnmanagedType для маршалинга строк.

Тип перечисления Описание неуправляемого формата
UnmanagedType.AnsiBStr Тип BSTR стиля COM с фиксированной длиной и символами в кодировке ANSI.
UnmanagedType.BStr Тип BSTR стиля COM с фиксированной длиной и символами Юникода.
UnmanagedType.LPStr (по умолчанию) Указатель на массив символов в кодировке ANSI, завершающийся значением null.
UnmanagedType.LPTStr Указатель на массив символов, завершающийся значением null, в зависящей от платформы кодировке.
UnmanagedType.LPUTF8Str Указатель на строку знаков в кодировке UTF-8, завершающуюся нулевым значением.
UnmanagedType.LPWStr Указатель на строку знаков в кодировке Юникод, завершающуюся нулевым значением.
UnmanagedType.TBStr Тип BSTR стиля COM с фиксированной длиной и символами в кодировке, зависящей от платформы.
VBByRefStr Значение, позволяющее Visual Basic изменять строку в неуправляемом коде и получать результаты, отраженные в управляемом коде. Это значение поддерживается только для вызова неуправляемого кода. Это значение по умолчанию для строк ByVal в Visual Basic.

Эта таблица применяется к String. Для StringBuilder единственными допустимыми вариантами являются LPStr, LPTStr и LPWStr.

В определении типа ниже показано правильное использование атрибута MarshalAsAttribute для вызовов неуправляемого кода.

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

Строки, используемые в структурах

Строки являются допустимыми элементами структур, но буферы StringBuilder недопустимы в структурах. В следующей таблице показаны параметры маршалинга String для типа данных, когда тип маршалируется как поле. Атрибут MarshalAsAttribute предоставляет несколько значений перечисления UnmanagedType для маршалинга строк в поле.

Тип перечисления Описание неуправляемого формата
UnmanagedType.BStr Тип BSTR стиля COM с фиксированной длиной и символами Юникода.
UnmanagedType.LPStr (по умолчанию) Указатель на массив символов в кодировке ANSI, завершающийся значением null.
UnmanagedType.LPTStr Указатель на массив символов, завершающийся значением null, в зависящей от платформы кодировке.
UnmanagedType.LPUTF8Str Указатель на строку знаков в кодировке UTF-8, завершающуюся нулевым значением.
UnmanagedType.LPWStr Указатель на строку знаков в кодировке Юникод, завершающуюся нулевым значением.
UnmanagedType.ByValTStr Массив символов фиксированной длины; тип массива определяется кодировкой содержащей его структуры.

Тип ByValTStr используется для встроенных массивов символов фиксированной длины, расположенных в структуре. Другие типы применяются к ссылкам на строки, включенным в структуры, содержащие указатели на строки.

Аргумент CharSet класса StructLayoutAttribute, применяемый к содержащей указатели структуре, определяет формат символов строк в структурах. Ниже приведены примеры структур, содержащих ссылки на строки и встроенные строки, а также символы в кодировках ANSI, Юникод и кодировке, зависящей от платформы. Представление этих структур в библиотеке типов показано в следующем коде C++:

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

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

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

В примере ниже показано, как с помощью класса MarshalAsAttribute определить одну и ту же структуру в различных форматах.

[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

Буферы строк фиксированной длины

При некоторых обстоятельствах необходимо передавать в неуправляемый код для обработки символьные буферы фиксированной длины. Простая передача строки в этом случае не работает, так как вызываемый объект не может изменять содержимое переданного буфера. Даже если строка передается по ссылке, не существует способа инициализации буфера заданного размера.

Решение заключается в передаче byte[] или char[](в зависимости от ожидаемой кодировки) в качестве аргумента вместо аргумента String. Массив, помеченный с [Out]помощью метки, может быть разыменован и изменен вызывающим объектом, если он не превышает емкость выделенного массива.

Например, функция API GetWindowText Windows (определенная в файле winuser.h) требует, чтобы вызывающий объект передавал буфер символов фиксированной длины, в который эта функция записывает текст окна. Аргумент lpString указывает на выделенный вызывающим буфер размером nMaxCount. Предполагается, что вызывающий объект выделяет буфер и задает аргумент nMaxCount равным размеру выделяемого буфера. В приведенном ниже примере показано объявление функции GetWindowText, определенное в файле winuser.h.

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

Может char[] быть разоменовано и изменено вызывающим элементом. В следующем примере кода показано, как ArrayPool<char> можно использовать для предварительного 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

Другим решением является передача StringBuilder в качестве аргумента вместо аргумента String. Буфер, созданный при маршалинге StringBuilder , может быть разоменовывлен и изменен вызывающим объектом, при условии, что он не превышает емкость StringBuilder. Его также можно инициализировать с фиксированной длиной. Например, если инициализировать StringBuilder буфер в емкость N, маршаллизатор предоставляет буфер размером (N+1). Дополнительный символ объясняется тем, что неуправляемая строка заканчивается символом null, а буфер StringBuilder нет.

Примечание.

Как правило, передача StringBuilder аргументов не рекомендуется, если вы обеспокоены производительностью. Дополнительные сведения см. в разделе "Строковые параметры".

См. также