- PVSM.RU - https://www.pvsm.ru -
Добрый день, уважаемые разработчики (просто не знал, с чего начать пост). Предлагаю перед тем как начнется трудовая неделя немного подразмять
Как все мы знаем, в .Net существует две группы куч: для больших и малых объектов. Как выяснить, во сколько нам обойдется объект можно при помощи кода из этой статьи (он нам пригодится): Ручное клонирование потока [3], а получить указатель на объект и по указателю получить сам объект можно научиться, прочтя эту статью: Получение указателя на объект .Net [4]. Также нам понадобится статья корейского (южно-) программиста по перенаправлению указателя на скомпилированную часть метода на другой метод: 실행 시에 메서드 가로채기 — CLR Injection: Runtime Method Replacer 개선 [5]
Так что давайте поэкспериментируем и напишем библиотеку, которая позволит:
Т.е. напишем пул объектов вне .Net памяти.
Будем решать задачи по мере поступления вопросов.
Как мы это можем сделать? Сначала я брал экземпляр существующего объекта, и вычитывал первые 4 или 8 байт, записывая их к себе. Метод работал, но он не красивый. Нужен экземпляр. После чего я нашел что этот адрес легко вычитывается с помощью свойства typeof(TType).TypeHandle.
Первое, что мы определяем – типы
internal static class Stub
{
public static void Construct(object obj, int value)
{
}
}
Это — тип, единственный статический метод которого будет изменен так, что указатель на скомпилированную часть метода будет смотреть на конструктор нашего типа. Первый параметр — obj — является по своей сути указателем this. Ведь, как вы знаете, this есть ни что иное как первый параметр метода, который есть всегда в каждом экземплярном методе. Второй параметр — целое число. Введен для того чтобы проверить что мы можем передавать целые числа.
public class UnmanagedObject<T> : IDisposable where T : UnmanagedObject<T>
{
internal IUnmanagedHeap<T> heap;
#region IDisposable implementation
void IDisposable.Dispose()
{
heap.Free(this);
}
#endregion
}
Далее введем тип UnmanagedObject чтобы во-первых ввести метод возврата объекта в пул Dispose(), а во-вторых архитектурно отделить все объекты, предназначенные для размещения вне CLR куч от стандартных. Единственное поле класса типа internal, чтобы его можно было задать извне, в пуле объектов.
И последнее — класс самого пула.
public unsafe class UnmanagedHeap<TPoolItem> : IUnmanagedHeap<TPoolItem> where TPoolItem : UnmanagedObject<TPoolItem>
{
private readonly IntPtr *_freeObjects;
private readonly IntPtr *_allObjects;
private readonly int _totalSize, _capacity;
private int _freeSize;
private readonly void *_startingPointer;
private readonly ConstructorInfo _ctor;
public UnmanagedHeap(int capacity)
{
_freeSize = capacity;
// Getting type size and total pool size
var objectSize = GCEx.SizeOf<TPoolItem>();
_capacity = capacity;
_totalSize = objectSize * capacity + capacity * IntPtr.Size * 2;
_startingPointer = Marshal.AllocHGlobal(_totalSize).ToPointer();
var mTable = (MethodTableInfo*)typeof(TPoolItem).TypeHandle.Value.ToInt32();
_freeObjects = (IntPtr*)_startingPointer;
_allObjects = (IntPtr*)((long)_startingPointer + IntPtr.Size * capacity);
_startingPointer = (void*)((long)_startingPointer + 2 * IntPtr.Size * capacity);
var pFake = typeof(Stub).GetMethod("Construct", BindingFlags.Static|BindingFlags.Public);
var pCtor = _ctor = typeof(TPoolItem).GetConstructor(new []{typeof(int)});
MethodUtil.ReplaceMethod(pCtor, pFake, skip: true);
for(int i = 0; i < capacity; i++)
{
var handler = (IntPtr *)((long)_startingPointer + (objectSize * i));
handler[1] = (IntPtr)mTable;
var obj = EntityPtr.ToInstance<object>((IntPtr)handler);
var reference = (TPoolItem)obj;
reference.heap = this;
_allObjects[i] = (IntPtr)(handler + 1);
}
Reset();
}
public int TotalSize
{
get {
return _totalSize;
}
}
public TPoolItem Allocate()
{
_freeSize--;
var obj = _freeObjects[_freeSize];
Stub.Construct(obj, 123);
return EntityPtr.ToInstanceWithOffset<TPoolItem>(obj);
}
public void Free(TPoolItem obj)
{
_freeObjects[_freeSize] = EntityPtr.ToPointerWithOffset(obj);
_freeSize++;
}
public void Reset()
{
WinApi.memcpy((IntPtr)_freeObjects, (IntPtr)_allObjects, _capacity * IntPtr.Size);
_freeSize = _capacity;
}
object IUnmanagedHeapBase.Allocate()
{
return this.Allocate();
}
void IUnmanagedHeapBase.Free(object obj)
{
this.Free((TPoolItem)obj);
}
public void Dispose()
{
Marshal.FreeHGlobal((IntPtr)_freeObjects);
}
}
По порядку:
В конструкторе класса мы сначала рассчитываем размер экземпляра типа. После чего умножаем на capacity, добавляем размер таблиц свободных/занятых слотов и получаем размер пула. Получив размер, аллоцируем пул в виртуальной памяти. После чего получаем описатели методов: конструктора типа и заглушки и у заглушки выставляем указатель на тело метода как тело конструктора:
var pFake = typeof(Stub).GetMethod("Construct", BindingFlags.Static|BindingFlags.Public);
var pCtor = _ctor = typeof(TPoolItem).GetConstructor(new []{typeof(int)});
MethodUtil.ReplaceMethod(pCtor, pFake, skip: true);
Последнее — в цикле, у каждого будущего объекта проставляем указатель на таблицу вирт методов, делаем кастинг в .Net тип и выставляем поле heap у только что проинициализированного объекта в наш пул.
Отдельный интерес представляет метод Allocate:
public TPoolItem Allocate()
{
_freeSize--;
var obj = _freeObjects[_freeSize];
Stub.Construct(obj, 123);
return EntityPtr.ToInstanceWithOffset<TPoolItem>(obj);
}
В нем мы сначала из таблицы свободных объектов берем последний из них. После чего вызываем метод Construct класса Stub, тело которого на самом деле — наш конструктор класса элемента пула. Конструктору передаем число 123 как параметр.
Использование протестируем с помощью следующего кода
using System;
using System.Runtime.CLR;
namespace UnmanagedPoolSample
{
class Program
{
/// <summary>
/// Now cannot call default .ctor
/// </summary>
private class Quote : UnmanagedObject<Quote>
{
public Quote(int urr)
{
Console.WriteLine("Hello from object .ctor");
}
public int GetCurrent()
{
return 100;
}
}
static void Main(string[] args)
{
using (var pool = new UnmanagedHeap<Quote>(1000))
{
using (var quote = pool.Allocate())
{
Console.WriteLine("quote: {0}", quote.GetCurrent());
}
}
Console.ReadKey();
}
}
}
Вывод в консоль:
Автор: sidristij
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/net/78938
Ссылки в тексте:
[1] мозги: http://www.braintools.ru
[2] Image: http://habrahabr.ru/company/luxoft/blog/246665/
[3] Image: http://habrahabr.ru/company/luxoft/blog/238947/
[4] Image: http://habrahabr.ru/company/luxoft/blog/219619/
[5] 실행 시에 메서드 가로채기 — CLR Injection: Runtime Method Replacer 개선: http://www.sysnet.pe.kr/2/0/942
[6] Источник: http://habrahabr.ru/post/247433/
Нажмите здесь для печати.