Маршалирование по умолчанию для строк
Как классы 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
аргументов не рекомендуется, если вы обеспокоены производительностью. Дополнительные сведения см. в разделе "Строковые параметры".