esyr: (Default)
Допустим (а такое таки иногда случается), у нас есть ряд классов, (как это обычно и бывает) потомков одного класса, для которых удобно делать вещи в духе
Entity := TEntity.Create(TSubEntity1.Create(...), TSubEntity2.Create(...), ...);
...
Entity.Free;

То есть иметь конструктор, в котором можно проинициализировать состояние объекта. Причём объект у нас сложный и может включать другие объекты. Которые, в свою очередь, могут включать третьи, и так далее. И вся эта радость создаётся в одном операторе. Удобно.

Но при таком подходе возникает проблема… )
Выход из такого положения очевиден… )
Одним из возможных достаточно универсальных решений является использование RTTI… )
Быдлокод )
Тут комментарии на русском, но это исключительно для читабельности (и из-за того, что с английским я дружу плохо). Ни в коем случае не используйте неанглийские комментарии в коде.
Механизм действия сего деструктора )
Ограничения )
Собственно, мне это понадобилось потому, что я таким образом создаю сообщения, которые надо сериализовать в поток. Потом подумал, что это будет полезно не только мне, и решил написать об этом.

В следующий раз будет что-нибудь про XML (Save/Load объектов, загрузка UI) и/или версионинг, смотря что доведу до презентабельного состояния первым.
esyr: (ночь)
Повбывал бы за отсутствие документации по дельфёвому RTTI. То немного, что удалось вытащить из неё, это то, что проперши, точнее, TPropInfo может иметь тип, точнее, TTypeKind следующих видов:
tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString, tkVariant, tkArray, tkRecord, tkInterface, tkDynArray
Дальше надо уже лезть в TypInfo.pas и ныкаться по всяким форумам. Но вот из того же TypInfo.pas совершенно непонятно, как по тому, что у нас есть экземпляр записи TTypeInfo с TTypeKind tkRecord, то есть, проперти-запись, узнать поля этой записи. Или по массиву узнать его характеристики (хотя, для массива таки есть GetTypeData (у которого, кстати, превосходный исходнк:
function GetTypeData(TypeInfo: PTypeInfo): PTypeData;
asm
        { ->    EAX Pointer to type info }
        { <-    EAX Pointer to type data }
        {       it's really just to skip the kind and the name  }
        XOR     EDX,EDX
        MOV     DL,[EAX].TTypeInfo.Name.Byte[0]
        LEA     EAX,[EAX].TTypeInfo.Name[EDX+1]
end;
), который выдаёт TTypeData — монструозную структуру, в которой таки есть размер динамического массива и тип элементов, но у типа тип PPTypeInfo, и что пока с этим делать, не совсем понятно, хотя…). На самом деле, обе проблемы обходятся (первая — вложенным сериализуемым классом, структура которого повторяет структуру записи, вторая — сериализуемым списком сериализуемых объектов), но как-то коряво.
И ещё надо не забыть про версионинг, хотя ещё не уверен, нужен ли он тут.
esyr: (Default)
Собственно, есть задача: уметь делать передачу каких-то структур по сети и сохранять их на диске. В С++ подобная задача очень элегантно решается через темплейты, напрмер, есть луноликая _Winnie Serialization Library. Но в Delphi такого мощного инструмента нет. Зато есть кое-какие другие.

Когда передо мной встала сначала такая задача, я начал решать её довольно криво: подготавливал в памяти буфер, в него складывал сериализованную структуру и потом уже кидал его по сети или сохранял на диск. Делалось это очень криво и через такую жопу, что я даже вспоминать боюсь (да и код я уже этот похерил). Соответственно, у такого подхода ряд недостатков:
  • Надо заранее знать размер буфера, что, в общем случае, нетривиально. Или делать аццкие реаллоки, что тоже не есть много хорошего
  • Надо процедурам сохранения кусков структур передавать много лишнего
  • Сам код достаточно кривой получается

В итоге, от сего счастья, к счастью, пришлось отказаться.

Тут вспомнилось, что в Delphi есть много чего хорошего в её бездонной VCL/RTL, например, такая абстракция как поток (TStream). Кроме того, Delphi она ж объектно ориентированная, посему логично обернуть сериализуемыю структуру в класс. Сделано это было приблизительно следующим образом (спасибо DRKB за многое количество полезной информации):
немного кода )
Теперь можно унаследовать от него, переписать SaveToStream/LoadFromStream и будет счастье. Причём, что приятно, они выглядят приблизительно следующим образом:

немного кода )
Да, ещё же нам надо сериализовывать списки. Это тоже решается довольно красиво, и тут уже нам требуется RTTI для воссоздания класса по сохранённому в потоке его имени (собственно, получание ссылки на класс по её имени в ReadObject):

немного кода )
Приятно то, что список является сериализуемым объектом, поэтому в save/load для него можно вызвать WriteObject/ReadObject и он кошерно сериализует себя вместе со своими элементами, которые также могут быть списками, и так далее.

Но есть у подобного подхода свои недостатки (исправляемые, как будет видно далее, эволюционными методами):
  • Надо для каждого класса расписывать Save/Load, а он в 99 процентах случаев однотипен: нетривиально его пришлось писать только для списка, в остальных случаях это просто сохранение/загрузка примитивов
  • Код save/load в этих 99 процентах случаев один и тот же в том смысле, что сначала в какой-то последовательности объекты сохраняются, а потом в той же восстанавливаются — дублирование кода и источник ошибок (перепутал порядок загрузки, забыл загрузить или сохранить, и так далее)
  • Случилось так, что в этих же 99 процентах сохраняются исключительно проперти объекта (плюс, возможно, некоторые private поля)


Теперь начинается самое интересное. Очевидно, что для решения перечисленных проблем нам нужно нечто, которое сериализует все проперти класса. И оно было написано как метод корневого класса TSerializableObject (показан только код LoadFromStream, SaveToStream аналогичен, только вместо SetType(ReadType(Stream), PropList^[I].Name); стоит WriteType(GetType(PropList^[I].Name), Stream);):

немного кода )
Теперь, можно просто объявить потомок в виде

немного кода )
и всё будет работать. Даже методов писывать не надо. Если же надо что-то записать дополнительно, то можно это сделать следующим образом:

немного кода )
На данный момент осталась проблема дублирования кода (как save/load, так и для каждого примитива), но это я попытаюсь решить введением ещё одного слоя абстракции типа procedure SerializeEntity();.

Пока вот так. //Ох и полетят же сейчас в меня камни и насмешки...

Profile

esyr: (Default)
esyr

October 2010

S M T W T F S
     12
3456789
10111213141516
17181920212223
24252627282930
31      

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Sep. 20th, 2017 11:36 pm
Powered by Dreamwidth Studios