[Resent; stupid me forgot to change the To: field, and I've been waiting for the past two days for a reply. :P ]
I apologize for the vagueness of my original post. It was written at the end of the very tiring day (or the start of one; depends on how you look at it).
Anyway, here's what I failed to provide the first time around. I hope this is much more useful.
I have several objects with no common parent, though all of them have a private member called structure. With each object, `structure' is of a different record type.
What I'd like to be able to do is write generic functions that would take any object and write its structure member into file, or read a structure in a file and assign it to said object's structure member.
I know I can make an abstract root object that implements a common inheritance for all the objects that need to be written to and read from a file, but I'm lazy, and since these functions will be doing basically the same thing (managing binding types, reading to/writing from streams), I'd like to write just one function for each job, which figures out how to bind bindable variables depending on which object/record I throw at it.
Oh, and Frank mentioned storing objects in streams; I'm kind of curious how this works; I'm not exactly new at OO, but I've never written objects to streams. All I've been doing so far is writing records inside objects into files.
Thanks for the help, guys. :)
Neil Santos wrote:
I apologize for the vagueness of my original post. It was written at the end of the very tiring day (or the start of one; depends on how you look at it).
I know. :-)
Anyway, here's what I failed to provide the first time around. I hope this is much more useful.
I have several objects with no common parent, though all of them have a private member called structure. With each object, `structure' is of a different record type.
What I'd like to be able to do is write generic functions that would take any object and write its structure member into file, or read a structure in a file and assign it to said object's structure member.
I know I can make an abstract root object that implements a common inheritance for all the objects that need to be written to and read from a file,
... using a virtual method. That would be the OOP approach.
but I'm lazy, and since these functions will be doing basically the same thing (managing binding types, reading to/writing from streams),
Which could be done in a common ancestor's method. (No-one ever said OOP would reduce the number of routines in total ... ;-)
BTW, if you don't want to be Extended Pascal conformant (and you obviously don't), you can use GPC extensions such as:
Rewrite (FileVar, FileName);
which saves you the trouble of dealing with Bindings yourself.
I'd like to write just one function for each job, which figures out how to bind bindable variables depending on which object/record I throw at it.
There is no such thing as runtime type information in GPC. What you could do is treat each record as an untyped block, using untyped pointers as you do, or untyped parameters, and a size (passing `SizeOf (Field)'), an in the routine use `BlockWrite' etc. You might need to make up a type ID yourself (if you need to; if you always know which type to expect in a file, this may not be necessary).
This presupposes, of course, that all such records are file writable, e.g., don't contain file fields, pointers (which become meaningless when read back), objects etc.
Oh, and Frank mentioned storing objects in streams; I'm kind of curious how this works; I'm not exactly new at OO, but I've never written objects to streams. All I've been doing so far is writing records inside objects into files.
That's basically the way to go. Don't write the object variable, but the individual fields (again, using virtual methods, so it's not exactly for the lazy ;-).
If objects refer to each other via pointers (as they often do), and you want to store such a structure, and/or if you need to store type information in the file (i.e., you don't always know in advance which type to expect), you need another mechanism. I think it can be written rather generally, but I don't know if someone has done it.
GPC internally uses a similar mechanism for writing GPI files (which basically are recursive structures, heavily pointing to each other), but it's written in C and somewhat specialized. I wrote a bit about it in the `Internals/GPI files' section of the manual, in particular under `GPI_CHUNK_NODES' and `GPI_CHUNK_OFFSETS'. (For orientation: `load_node()' and `store_node_fields()' would correspond to the virtual methods, `TREE_CODE' corresponds to the object type (*). The re-exporting stuff is irrelevant unless you want to handle several files with objects that refer to each other and have unique identities ... why not? :-) The description (like this mail ;-) is a bit terse and probably not completely understandable without looking at the source (module.c) or asking again ...
(*) In GPC, you get type information for objects using `TypeOf' (a so-called VMT pointer), but for these purposes, this is basically a pointer that is the same for same types and different for different ones. Though you can get the name of the object, its size and the parent relation from it (see `PObjectType'). The size is not needed for storing (because you don't store the object as a "blob"), but it might be useful for loading. Basically the loading process would go like this (in a general framework):
- Load type ID from file.
- Get object type pointer (PObjectType) from type ID. This is a mapping you have to do yourself. One idea is to assign a unique ID to each object type (currently this would require a virtual method, since we don't have class variables/constants), and register each object type in a hash table or similar.
- Get the size from the PObjectType and allocate untyped memory (GetMem) to a pointer to the base object type (there should be a single one). If you like, zero it out.
- Store the type pointer using `SetType'.
- Let the object load itself by calling the virtual method (this will work now, since `SetType' has set the VMT pointer which is needed in virtual method calls). (Basically this would be a "virtual constructor", but GPC doesn't support them (yet?). But a virtual method will do just as well.)
- The loading methods must note that all fields are uninitialized (whether or not you zeroed them). E.g., if there are string fields, they can't just assign a value to them (not even ''), but must call `Initialize' for each field, or load the field as a blob from the file if possible (for strings, it would be possible, if they were stored as such).
It is a bit of work, but the most tricky parts can be written in a rather general fashion, so it would be a one-time work if someone is interested to do it ...
Frank
On 9 Mar 2004 at 2:56, Frank Heckenbach wrote:
Neil Santos wrote:
[....]
- The loading methods must note that all fields are uninitialized (whether or not you zeroed them). E.g., if there are string fields, they can't just assign a value to them (not even ''), but must call `Initialize' for each field, or load the field as a blob from the file if possible (for strings, it would be possible, if they were stored as such).
It is a bit of work, but the most tricky parts can be written in a rather general fashion, so it would be a one-time work if someone is interested to do it ...
The FreeVision objects unit (which I ported to GPC) has TStream objects of course, and all objects descending from TObject are streamable. So much of the work has already been done. The objects unit (full sources) can be obtained from here: http://www.gnu-pascal.de/contrib/chief/objects/
Best regards, The Chief -------- Prof. Abimbola A. Olowofoyeku (The African Chief) web: http://www.bigfoot.com/~african_chief/