Поделиться через


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

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

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

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

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

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

Эта таблица относится к 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);
};

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

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

Нативный код отвечает только за освобождение памяти, если строка передается по ссылке и ей назначается новое значение. В противном случае среда выполнения .NET владеет памятью и отпустит ее после вызова.

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

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

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

В следующем определении типа показано правильное использование MarshalAsAttribute для вызовов платформы platform invoke.

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 COM-стиль BSTR с префиксной длиной и символами Юникод.
UnmanagedType.LPStr (по умолчанию) Указатель на массив символов ANSI, завершающийся значением NULL.
UnmanagedType.LPTStr Указатель на массив символов, зависимых от платформы, завершаемых значением NULL.
UnmanagedType.LPUTF8Str Указатель на массив символов, завершаемых значением NULL, в кодировке UTF-8.
UnmanagedType.LPWStr Указатель на массив символов Юникода, завершающийся значением NULL.
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 Windows GetWindowText (определенная в 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) символов. Функция +1 учитывает тот факт, что неуправляемая строка имеет конечный элемент NULL, а StringBuilder не имеет.

Замечание

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

См. также