Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Runtime Compiled C++

2 комментария

Нынче грех жаловаться на компиляцию. Я вот смотрю на то, что сейчас возможно в последней версии MSVC — там по сути компиляция инкрементальна не на уровне файлов (де компилим только те файлы, которые поменялись), а на уровне функций — остальное берется из кэша. Как оно точно работает я не знаю, знаю лишь что при изменениях я вижу нечто вроде

166 of 3863 functions ( 4.3%) were compiled, the rest were copied from previous compilation.
4 functions were new in current compilation
335 functions had inline decision re-evaluated but remain unchanged

Что бы это не значило, результатом является то, что перекомпиляция незначительных изменений в C++ программе теперь практически моментальна, хотя насколько это дело масштабируется еще нужно понять.

Вообщем, рассказ-то у меня не про это, а про runtime-компиляцию, про которую я уже часто говорил под термином ‘динамическое прототипирование’ — вот тут у меня .NET-ное видео есть, можно посмотреть как это делать на C#-е.

Вообще этот подход хорошо иллюстрирует зачем вообще нужен Dependency Inversion Principle — да-да, именно inversion а не injection. Если попытаться объяснить этот принцип словами, получится что-то вроде этого:

Делайте Extract Interface на любом классе, по поводу и без повода. Замените все типы с Foo на IFoo. Если кому че не нравятся, пусть пишут официальную жалобу, в дубликате и с ксерокопией паспорта.

Вот как-то так. А вы думали, зачем Extract Interface? Конечно чтобы в рантайме заменить один тип на другой. Как? Объясняю:

  • Вы сделали объект типа Person.

  • В рантайме вы поменяли объект типа Person, скомпилировав еще один такой же но с пофикшенной багой.

  • Теперь у вас два объекта типа Person, но это разные типы!

Никакого вам duck typing’а! Следовательно, вы в принципе не можете зависеть от Person, а только от некого интерфейса IPerson который, кстати, менять нельзя если только вы не абстрагировались еще выше (но боюсь что это непрактино в 99% ситуаций).

А что в C++?

Да по сути то же самое, только еще проще т.к. ничего не надо делать, умные дяди уже все сделали. Естественно, что этот подход сугубо intrusive, т.к. это «плюсы» и тут нету reflection и иже с ним.

  • Файлы которые нужно подменить помечаются специальным макросом REGISTERCLASS.

  • В момент запуска вашей проги, система строит табличку того какой класс где определен.

  • Классы, которые хочется менять в рантайме есессно не положено инстанцировать «в лоб», так что

    IObjectConstructor* pCtor = m_pRuntimeObjectSystem->GetObjectFactorySystem()->GetConstructor( "RuntimeObject01" );
    if (pCtor)
    {
    	IObject* pObj = pCtor->Construct();
    	pObj->GetInterface( &m_pUpdateable );
    	if( 0 == m_pUpdateable )
    	{
    		delete pObj;
    		m_pCompilerLogger->LogError("Error - no updateable interface found\n");
    		return false;
    	}
    	m_ObjectId = pObj->GetObjectId();
    }
    

На этом этапе у кого-то может политься кровь из глаз, но я напоминаю, что это C++. Выше происходит следующее: мы через нашу runtime систему находим фабрику которая инстанцирует объект и вызываем конструктор, а потом пытаемся подкрутить его к интерфейсу — если получилось, сохраняем также id объекта чтобы его потом можно было правильно удалить.

Значит теперь к объекту мы можем достучаться только через его интерфейс. Интерфейс — это просто структура с pure virtual функциями, наподобии вот этой:

struct IUpdateable : public IObject
{
  virtual void Update(float deltaTime) = 0;
};

IObject — это важный базовый класс инфраструктуры. Ну так вот, что там дальше?

  • После запуска, система начинает мониторить все изменения. Если файл поменялся, этот файл (а также все его зависимости) собирается в отдельную динамическую либу.

  • Мы загружаем либу в процесс, сериализуем состояние старых объектов, подменяем его новыми реализациями, и потом десериализуем состояние обратно. При этом что делать если структура объектов поменялась — об этом мы не говорим. Подразумевается, что публичный интерфейс не может меняться, и ладно.

  • Внешние компоненты, которые сами по себе не являются runtime-modifiable могут отлавливать события когда меняется структура изменяемых объектов и как-то на это реагировать.

Вообщем, все это очень интересно и, резюмируя, является еще одним козырем в рукаве, наряду с Cling, обертыванием C++ либ в Питон, и прочими извращениями. Enjoy!

Advertisements

Written by Dmitri

2 февраля 2016 в 2:01

Опубликовано в С++

Tagged with

комментария 2

Subscribe to comments with RSS.

  1. Делайте Extract Interface на любом классе, по поводу и без повода. Замените все типы с IFoo на Foo. Если кому че не нравятся, пусть пишут официальную жалобу, в дубликате и с ксерокопией паспорта.

    Наверное наоборот? С Foo на IFoo? Откуда цитата?

    Andrei Vikulov

    3 февраля 2016 at 10:41

    • Да, вы правы. Я поправил пост. Цитата это из моей головы :)

      Dmitri

      3 февраля 2016 at 11:07


Оставить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: