Реализация
Класс TRttiObject // Сохраняет и читает из потока все Published свойства TRttiObject = class(TInterfacedPersistent, IStreamPersist) public procedure SaveToStream(Stream: TStream); procedure LoadFromStream(Stream: TStream); constructor Create; virtual; end;
Тут нужно немного пояснить. Класс будет записывать/читать все свойства, тип которых целый (в том числе логический), перечислимый, вещественный, символьный, строковый, а также некоторые классы, которые поддерживают работу с потоками. Отличить эти классы от других, можно запросив у них интерфейс IStreamPersist, который объявлен в Classes.pas так:
IStreamPersist = interface ['{B8CD12A3-267A-11D4-83DA-00C04F60B2DD}'] procedure LoadFromStream(Stream: TStream); procedure SaveToStream(Stream: TStream); end;Этот класс реализуют, например, все потомки TGraphic, такие как TBitmap, TIcon, TMetafile, а также наш класс TRttiObject.
Почему в качестве предка выбран TInterfacedPersistent, а не TObject или TInterfacedObject. Тут несколько причин: во-первых, он является потомком TPersistent, который объявлен с директивой {$M+} (правда ничего не мешало бы сделать это самим), а во-вторых, в нем наиболее удачно для нас реализованы методы интерфейса IInterface (подсчет ссылок, реализованный в TInterfacedObject, нам ни к чему, а если взять TObject, то эти методы нужно будет реализовать самому).
procedure TRttiObject.SaveToStream(Stream: TStream); var TypeData: PTypeData; PropList: PPropList; Count,i: Integer;// Локальные процедуры procedure WriteOrdProp; // Запись целых и перечислимых данных var Value: Integer; begin Value:=GetOrdProp(self,PropList[i]); Stream.Write(Value,SizeOf(Value)); end; procedure WriteFloatProp; // Запись вещественных данных var Value: Extended; begin Value:=GetFloatProp(self,PropList[i]); Stream.Write(Value,SizeOf(Value)); end; procedure WriteStringProp; // Запись строки var Value: String; L: Integer; begin Value:=GetStrProp(self,PropList[i]); L:=Length(Value); Stream.Write(L,SizeOf(L)); Stream.Write(PChar(Value)^,Length(Value)); end; procedure WriteClassProp; // Запись класса var Obj: TObject; SaveLoader: IStreamPersist; IsEmpty: Boolean; begin Obj:=GetObjectProp(self,PropList[i]); if (Obj is TGraphic) then begin IsEmpty:=TGraphic(Obj).Empty; Stream.Write(IsEmpty,SizeOf(Boolean)); end; if Supports(Obj,IStreamPersist,SaveLoader) then begin SaveLoader.SaveToStream(Stream); end; end; // Собственно сама процедура поиска свойств и записи begin TypeData:=GetTypeData(ClassInfo); // Получаем указатель на информацию Count:=TypeData.PropCount; // Получаем количество свойств if Count>0 then begin // Выделяем память для списка свойств GetMem(PropList,SizeOf(PPropInfo)*Count); Try // Получаем список свойств GetPropInfos(ClassInfo,PropList); // Перебираем все свойства из списка и сохраняем их // в поток в соответствии с их типом for i:=0 to Count - 1 do begin case PropList[i].PropType^.Kind of tkEnumeration, tkInteger, tkChar, tkWChar: WriteOrdProp; tkFloat: WriteFloatProp; tkString, tkLString: WriteStringProp; tkClass: WriteClassProp; end; end; finally // Освобождаем память FreeMem(PropList,SizeOf(PPropInfo)*Count); end; end; end;
Я не буду подробно комментировать каждую функцию из TypInfo.pas, по комментариям сами разберетесь, прошу только обратить внимание на локальную процедуру записи класса. Сначала мы получаем экземпляр самого объекта. Потом проверяем, не является ли он потомком TGraphic. Далее записываем в поток, является ли графический объект пустым. Дело в том, что если объект (например Bitmap) пустой, то вызов SaveToStream не запишет в поток ничего. При чтении объект не сможет узнать о том, что он должен быть пустым, и будет, как ни в чем не бывало читать следующие по очереди данные из потока. Само собой это вызовет ошибку. Честно говоря, мне не очень нравится, как я решил эту проблему. Если у Вас есть идеи получше - пишите в обсуждении статьи.
И в конце процедуры WriteClassProp самое главное. Запрашиваем интерфейс IStreamPersist и заодно проверяем, поддерживает ли вообще его объект. Если да, то вызываем метод интерфейса SaveToStream.
Процесс чтения свойств из потока аналогичен. Я не буду его рассматривать в статье, в прилагаемом примере вы его найдете и сами сможете разобраться.