Когда использовать Span<T> вместо Marshal при вызове собственных API Win32?

Мне нужно вызвать API Win32, который выдает строку с нулевым завершением в буфер, а затем читает эту строку из буфера.

Недавно я узнал о Span<T> и пытаюсь понять, следует ли мне его использовать или делать что-то традиционным способом, используя класс Marshal.

Ниже вы увидите два разных способа достижения одной и той же цели: один с помощью Span<T>, другой с помощью Marshal.

Я хочу знать, разумно ли я здесь использую Span<T>? И будет ли производительность такой же или лучше при использовании Span<T>?

И в качестве примечания: существует ли «более правильный» способ извлечь строку с нулевым завершением из Span<byte>, или я это делаю нормально?

Использование Маршала:

[DllImport("hid.dll", SetLastError = true)]
public static extern bool HidD_GetProductString(IntPtr HidDeviceObject, IntPtr Buffer, uint BufferLength);

public static string GetProductNameUsingMarshal(IntPtr handle)
{

    var stringPtr = Marshal.AllocHGlobal(254);

    try
    {
        if (!Win32.HidD_GetProductString(handle, stringPtr, 254)
        {
            throw new Win32Exception(Marshal.GetLastPInvokeError(), Marshal.GetLastPInvokeErrorMessage());
        }

        return Marshal.PtrToStringUni(stringPtr);
    }
    finally
    {
        Marshal.FreeHGlobal(stringPtr);
    }

}

Использование диапазона:

[DllImport("hid.dll", SetLastError = true)]
public static extern bool HidD_GetProductString(IntPtr HidDeviceObject, ref byte Buffer, uint BufferLength);

public static string GetProductNameUsingSpan(IntPtr handle)
{

    var buffer = new Span<byte>(new byte[254]);

    var success = Win32.HidD_GetProductString(handle, ref MemoryMarshal.GetReference(buffer), 254);

    if (!success)
    {
        throw new Win32Exception(Marshal.GetLastPInvokeError(), Marshal.GetLastPInvokeErrorMessage());
    }

    var charSpan = MemoryMarshal.Cast<byte, char>(buffer);

    return new string(charSpan.Slice(0, charSpan.IndexOf(char.MinValue)));

}

Почему вы делаете все это вместо того, чтобы поставить [Out] string Buffer?

GSerg 20.05.2024 14:29

[DllImport("hid.dll", SetLastError = true)] public static extern bool HidD_GetProductString(IntPtr HidDeviceObject, [Out] out string Buffer, uint BufferLength); вар успех = Win32.HidD_GetProductString (дескриптор, выход вар thestring, 254); System.AccessViolationException: 'Попытка чтения или записи защищенной памяти. Часто это указывает на то, что другая память повреждена».

NoPyGod 20.05.2024 14:33
[Out] string Buffer, а не [Out] out string Buffer.
GSerg 20.05.2024 14:39

Сначала попробовал, тот же результат.

NoPyGod 20.05.2024 14:39

Вы выделили ли вы память в этой строке перед вызовом (сделали ее длиной 254 символа)? Альтернативно, StringBuilder Buffer и передайте StringBuilder с Capacity из 254.

GSerg 20.05.2024 14:42
Learn.microsoft.com/en-us/dotnet/fundamentals/code-anaанализ/‌​… Избегайте параметров StringBuilder для P/Invokes
NoPyGod 20.05.2024 15:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
101
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Между ними нет большой разницы. Marshal старше, но в некотором смысле безопаснее для PInvoke, поскольку не мешает управляемой куче. Span может быть лучше во многих отношениях, поскольку позволяет напрямую обрабатывать буферы без копирования и назначать их обычному byte[] массиву в управляемой куче.

Что бы вы ни делали, не приводите ref к указателю *, иначе вы получите дыру в GC (GC не может отслеживать указатели * и может переместить или перезаписать буфер).

Но и то, и другое ненужно. Вы можете передать буфер byte[] напрямую.

[DllImport("hid.dll", SetLastError = true)]
public static extern bool HidD_GetProductString(IntPtr HidDeviceObject, [Out] byte[] Buffer, uint BufferLength);

public static string GetProductNameUsingSpan(IntPtr handle)
{
    var buffer = new byte[254];
    var success = Win32.HidD_GetProductString(handle, buffer, buffer.Length);
    if (!success)
    {
        throw new Win32Exception(Marshal.GetLastPInvokeError(), Marshal.GetLastPInvokeErrorMessage());
    }

    var charSpan = MemoryMarshal.Cast<byte, char>(buffer.AsSpan());
    return new string(charSpan.Slice(0, charSpan.IndexOf(char.MinValue)));
}

Обратите внимание, что 254 — это количество байтов, вы получаете только половину символов UTF16 (и вычтите 1 для терминатора NUL).

Для других функций, возвращающих ANSI, прямое приведение к char совершенно неверно. Вам нужно использовать Encoding.ASCII.GetString, чтобы перекодировать его.

Другие вопросы по теме