Pull to refresh

Comments 10

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

Я не уверен, что название статьи соответствует ее содержанию. Что вы подразумеваете под "созданием указателей"? Создать указатель несложно:

byte* ptr = (byte*)42;, выполненное в unsafe-контексте.

Может быть вы имели ввиду IntPtr/UIntPtr? Чем не указатели? Создать можно вообще оператором new.

Еще есть "управляемые" (managed) указатели (или ссылки, кому как удобнее). Очень легко создаются:

ref var ptr = ref myObj.myField;

По тексту же оказывается, что речь идет о способах выделения памяти, которую потом можно выставить в неуправляемый код, и которую GC не будет перемещать (по крайней мере пока длится некая заданная операция).

И тут да, stackalloc заходит хорошо -- если вам нужно хранить пару сотен байт. И если копнуть чуть глубже, выяснится, что в спецификации все прекрасно написано. Есть лимит на размер стэка, и в зависимости от, например, количества вызванных методов, stackaclloc может как работать, так и валиваться с исключением.

Затем идет fixed -- в принципе логично, но почему-то не упомянут GCHandle.Alloc(..., Pinned), который тоже прибивает гвоздями целый объект пока вы не освободите полученный хэндл. И тоже можно достать указатель. Заплатив правда оверхэд за приколачивание памяти к указателю.

С Marshal.Alloc* не все понятно. Было бы неплохо объяснить разницу и еще уточнить, что произойдет, если вызвать AllocCoTaskMem() на ОС отличной от Windows (где COM просто нет). Я например не знаю (но теперь видимо попробую, т.к. на MSDN про это не сказано).

Ну и наконец, где же Pinned Object Heap? Штука ведь специльно для этого, доступна с .NET 5, целый год этой фиче уже. В случае POH fixed операция становится бесплатной. Вангую, что какой-нибудь Unsafe.AsPointer(ref pohArr[0]) будет тоже бесплатным.

При этом стоит уточнить, что если речь идет о вызове каких-то нативных методов, то встроенный маршалер может справиться со многими задачами сам, если ему правильно накинуть атрибутов на импортируемую функцию. Могу ошибаться, но он кажется теперь даже со Span работает как надо. А если речь идет не о массивах, а о, например, структурах (типичный паттерн -- создаете структуру, отдаете ее методу, который заполняет в ней поля, после чего используете ее для конфигурации), то там вообще достаточно out/ref/in (возможно, с дополнительными атрибутами). И никаких указателей в том смысле, в котором вы о них тут рассуждаете, не надо.

Поправьте/дополните, если что-то упустил/наврал.

Отвечу сам себе:

Code

using System;
using System.Runtime.InteropServices;
var ptr = IntPtr.Zero;
try
{
    Console.WriteLine("Allocating memory...");
    ptr = Marshal.AllocCoTaskMem(4097);
    Console.WriteLine($"Memory allocated at 0x{ptr.ToInt64().ToString("X16")}");
}
catch(Exception e1)
{
    Console.WriteLine($"Failed to allocate memory: \"{e1.Message}\"");
}
finally 
{
    if (ptr != IntPtr.Zero) 
    {
        Console.WriteLine("Freeing memory...");
        try 
        {
           Marshal.FreeCoTaskMem(ptr);
        }
        catch(Exception e2)
        {
            Console.WriteLine($"Failed to free memory >_< : \"{e2.Message}\"");
        }
    }
}

Далее делаем dotnet run:

  • Windows 10 x64

    • Allocating memory...

    • Memory allocated at 0x000001ABB8D3D350

    • Freeing memory...

  • Ubuntu 20.04 @ WSL2

    • Allocating memory...

    • Memory allocated at 0x0000557081DED9E0

    • Freeing memory...

Может это фича WSL на Windows, но похоже работает.

А еще не забываем про класс NativeMemory, который, как несложно догадаться, аллоцирует именно нативно (в частности, можем аллоцировать выровненную (aligned) память с помощью NativeMemory.AllocAligned)

Спасибо, по какой-то причине я это совсем упустил. Если я правильно понял по дате, замерджили это совсем недавно, и доступно это API будет не раньше .NET 6?

Да, это фича начиная с шестого дотнета (возможно в седьмом превью уже есть, не помню)

комментарий полезней статьи, спасибо за наводки на POH.

Зря вы назвали первую функцию Malloc. При первом прочтении может сложиться впечатление, что это была объявлена специальная функция для выделения памяти, хотя по сигнатуре становится ясно, что это не так. Тем более, что указатель на объект, выделенный на стеке было бы бессмысленно и опасно возвращать из функции. Эта память будет освобождена после завершения Malloc() и переиспользована.

Это всё конечно очень круто, но мне почему-то всегда казалось, что для работы с unmanaged библиотеками нужно использовать P/Invoke, а не выделять руками память. Делал (и в общем-то продолжаю делать) managed врапперы для нескольких немаленьких unmanaged библиотек различного рода, как-то так сложилось что возможностей P/Invoke для этих целей было всегда более чем достаточно.

Это ортогональные, то бишь независимые, вещи.

Ситуации, когда требуются указатели, не всегда сводятся к вызову внешнего кода. Хотя сейчас C# и BCL развиваются в сторону сокрытия от разработчика действий в unsafe-контексте.

Я понимаю, это больше к мотивации в начале статьи:
Указатели необходимы к примеру, для взаимодействия с библиотеками, использующими ручное выделение/удаление блоков памяти
Sign up to leave a comment.

Articles