开发者

SizeOf a dynamic structure

This will (hopefully) get solved pretty fast, this is my problem:

I have a structure

开发者_开发知识库
PMacro = ^TMacro;
  TMacro = class
    Hotkey: Integer;
    Command: String;
    CTRLMode: boolean;
    RepeatInterval: integer;

    constructor Create(Hotkey: Integer; Command: String; CTRLMode: boolean; RepeatInterval: integer); overload;
    constructor Create; overload;
    procedure Execute;
  end;

and I need to get its size (to save through TFileStream). Instances of this class are stored in a list elsewhere and this is my saving routine:

Stream:=TFileStream.Create(FileName,fmCreate or fmOpenWrite);
  for i := 0 to Macros.Count-1 do
  begin
    Macro:=TMacro(Macros[i]);
    Size:=sizeof(Macro);
    Stream.Write(size,SizeOf(integer));
    Stream.Write(Macro,sizeof(Macro));
  end;

The SizeOf(Macro) returns 4bytes, that would be the pointer, but i need the actual space the particular instance takes. The first thing that came to my mind is to get Length(Command) since it is a dynamic structure that returns size of its pointer. But that would mean to have something like SizeOf(Integer)+Length(Command)+SizeOf(boolean)+... but that would be bad for further expanding of the TMacro structure.

So, is there a way to get size of a structure that contains a dynamic type?

Thank you for answers


If you want to get the size of TMacro, call the InstanceSize method. But that won't help you blockwrite it to a stream, and it won't change to include the size of the string, because the string is a reference type.

You can't blockwrite your TMacro structure like that. First off, it's a class, not a record, which means it contains a "magic" field (or two of them, if you're using Delphi 2009 or later) that you don't want to save. Second, even if it was a record, it still contains a reference type (the string), so the data isn't stored inline in the TMacro; it's stored on the heap and has to be accessed individually.

If you need to implement serialization, you can do it a couple different ways. Either create a pair of methods like so:

procedure Load(savefile: TStream); //can also be implemented as a constructor
procedure Save(savefile: TStream);

and then implement them as reading/writing each field out one-by-one, or use a generic serializer of some sort with RTTI. This is a lot easier to write in Delphi 2010, since it has a much broader set of RTTI functionality available.


Since a Delphi object is a reference type, SizeOf() returns the size of a reference, which is the same as the size of a pointer, 4 bytes in current Delphi versions.

If you had the data in a record which is a value type then SizeOf() would return the size of the contents.

However, since your structure contains managed types, i.e. the string, you cannot simply save it in one big glob like this. You need special handling for the string.

If I were you I would save the information item by item. In particular this gives you control of issues like alignment and allows you to cater for versioning. You can easily enough write your own very basic code to do this. However, you may want to consider using a 3rd party framework if:

  • There are a lot of fields to persist and handling them individually would result in very laborious code.
  • You want to build in some flexibility for versioning of the file format. For example you may wish to think about what happens in a future version of the software when you add a new field, change the meaning of an existing field etc.


If you wanted SizeOf to give you the size of a record which you could then binary-persist, you could (although I don't personally recommend binary record persistence) use a RECORD type.

I believe that clarity on this issue requires not only David's answer, and Masons, but also the corollarly principle:

If SizeOf(RECORDTYPE) is what you want to figre out, first use RECORD (not Class), second use 100% value types like Char array (not String) results in a Binary Persistable record:

  type
    TMyCharType = UnicodeChar; // or AnsiChar. Your choice.  
    PMacro = ^TMacro;
    TMacro = record
      Hotkey: Integer;
      Command: Array [0..1000] of TMyCharType;  
      CTRLMode: Boolean;
      RepeatInterval: integer;
    end;

As a matter of style, I would tend to prefer a class-based system that uses some more advanced style of persistence than record-based binary storage. But if that's what you WANT to do, then use RECORD, as I say, not Class, and don't use String either.

Further, note that in your code sample, PMacro = ^TMacro is really worse-than-wrong, if TMacro is a class. (Unless you really mean to do some kind of double pointer indirection.)

TMacro (reference-types) are already by-reference, so there's no need to take the address of them, because they are passed internally between variables of type TMacro (if it's a class) as a pointer. So you really really need to get some clarity on Record types and Value types in your code.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜