Peter Gerwinski wrote:
Couldn't "Load" be a virtual method instead of a constructor (perhaps a Boolean function that returns if it didn't fail)?
Yes. IMHO, it could be in BP as well.
I never used TV, but I'd think so, too.
And it would be even better if the compiler could do this automatically, i.e. each object type could (optionally) declare an "ObjType" value, and the compiler would provide functions to get the ObjType from a VMT link and vice versa.
Sounds nice, but how to find a unique number for each object? Derive it from the name?
I don't think it's possible (or sensible) that the compiler derives it automatically. I thought about this problem several times, but didn't find a "good" solution. The name? It can change (if you change your naming conventions, or just don't like the name you chose), and it's against the "rule" that the identifiers should not influence the compiled code (I'm not sure if this is actually a rule in the Pascal syntax, but anyway I personally like it this way).
Also, there are other questions involved. If the code of the object type changes, should it get a new ID or not? I think, only the programmer can decide this. If all the object's fields and their semantics stay the same, the ObjID can usually stay the same. Also, of course, if the object is in the developing/debugging process. But if the "interface" of an object in a published program changes, the ID should change as well, to avoid compatibility problems with older stream files.
Bottom line: I don't think it can be done all automatically, but it can be done much more comfortably to the programmer than in TV.
What I meant was that the programmer declares the ID, like
type MyObject(TObject) Const ObjID:Word = MyBaseID+42; ... End;
or ... why not also
Const ObjID:Word = Inherited ObjID+1;
(Though the simple "+1" might not be good, as it can easily lead to conflicts - but perhaps something similar.)
Concerning the numbering convention, I'd also suggest something "better" than TV's way, perhaps an ID consisting of 4 parts (perhaps 16 bits for each part, this would leave room for a growing number of programs for some time (well - at least as long as 640 KB are enough for everybody... ;-), and the whole thing would be 64 bits (longlongint?)):
1. "unique" programmer ID 2. ID for a "category" of types (graphical objects, mathematical objects, dialog elements, ...) - unique only for the programmer 3. ID for a certain type within a category 4. "version number" for the type (i.e., if the fields or their semantics change, the version number will be changed, too, so that old objects in streams can be recognized)
Then what about storing the name of the object itself in the Stream?
As I said above, I wouldn't like this. But it could be an idea to have ObjID be a string instead of a numerical value. But I'm not sure if it's worth the additional work required...
Thinking about how to implement this, I realize this is basically the same as object constants -- something that's missing in BP and I could've used sometimes. This could be a useful extension to BP's objects. (The constants and their values would be inherited if not redeclared, just like virtual methods.)
And they would be stored in the VMT. Sounds reasonable.
Exactly. They would need to be _typed_ constants then, wouldn't they? (But there's no problem about it, except that the programmer has to type a little bit more...)
Just an addition to the class variables (this might complicate matters a bit more, though): The question is, should the variables be shared with child types or duplicated? I think there could be applications for both. To make clear what I mean, an example:
Type "a" declares a class variable "v". Type "b(a)" doesn't redeclare "v", so it uses the same variable as "a". Type "c(a)" redeclares "v", so it gets a variable of its own (of course, it must be of the same type as "a.v", but it could have a different initializer).
Does this seem reasonable?
To implement this, there must be a pointer to the actual variable in the VMT (which would point to the same variable for "a" and "b", and to a different variable for "c").
Sounds good, but I think the "stabilization" of gpc-2.1 has higher priority. But this might be of interest for gpc-2.2 ...
I (at least) am not in a hurry (now)... ;-)
Pierre Phaneuf wrote:
And also not put all the symbols in every programs, just those that use the "ClassName()" function, optimizing those using constants as their parameter and so on...
Perhaps it's easier to let "ClassName" be a normal object constant (if they'll be implemented). Then the programmer could decide which names to include in the program and how to call them (e.g. the actual identifier might be "TSomething", but for the ClassName it might be preferable to have just "Something").
The way I see it, the best way is to make a legal way to directly call a specific class constructor. Like the "typed new()" function (passing an object type as the first parameter) does, but being able to put a variable there. Maybe a GPC-specific new() function? With another name of course to avoid incompatibilities...
This would require something like "virtual constructors", wouldn't it? I.e., the constructors would have to have the same parameters in all classes, and their addresses would be stored in the VMT. Doesn't seem impossible, perhaps it's the most logical thing to do.
Peter Gerwinski wrote:
Look whether the VMT field contains a nonzero value. To be absolutely sure, we can even check whether the pointer really points to a VMT: The first Word pointed to must be a positive Integer (the size of the object), the second one its negative (which is included for purposes like this one). Problem: This will only work if the object was pre-initialized to have a zero VMT field. This can be achieved with `New', but not with `GetMem', so you have still to be careful when implementing streams.
And what about non-dynamic objects (i.e. in the global data or the local data of a procedure)? They're not often used as it seems, but AFAIK it's legal to do so -- and I do sometimes. In order to rely on this method, the compiler would have to initialize their VMT fields to 0, even if the variable is "uninitialized".
Then we could be sure in all cases but GetMem, and since GetMem is a "dirty trick", it's the programmer's responsibility to set the VMT to 0 (or whatever) after GetMem'ming.
Are we looking for BP compatibility or not?
We are in part. The primary goal of GPC is to produce the best Pascal compiler the world has ever seen.
I agree with Peter. I, FWIW, didn't use TV -- partly because some parts of it (including class registration) were, IMHO, badly implemented. If gpc's TV would be the same, I probably wouldn't use it, either.
(See the Info documentation for details.) BP compatibility is desireable because (ii) BP is not too bad and (i) many BP programmers will try GPC only if they can use their existing programs without any change.
So we may need both: a "100% BP compatible" unit for quick changes, and a "better" gpc unit to which programs can be converted afterwards, and which new programs can use from the beginning. Actually, I think, the changes needed for BP programs shouldn't be too big: make the "Load" constructor virtual (if we do it this way), declare the "ObjID" constant, and remove the registration stuff. Could even be simplified with some "IFDEF"s and clever use of the preprocessor, I guess.
How does Delphi do this? (Sorry for the stupid question, but my experience with Delphi is very limited, primarily because I cannot stand the mouse-only "user interface".)
Argh! Mouse-only for a programmers' tool??? Lucky me that I didn't buy it!
What about making the structure of the VMT record visible for the program? For instance a built-in
Type VmtRec = record Size, NegSize: Integer; VirtualMethod: array [ 0..1 ] of Pointer; end (* VmtRec *);
Where the `1' above stands for the unknown number of methods in a VMT.
Then it should be "0..0" or "1..1", but I prefer the following.
(* I thought about changing the VMT to a schema type
Type VmtRec ( n: Integer ) = record Size, NegSize: Integer; VirtualMethod: array [ 1..n ] of Pointer; end (* VmtRec *);
which would place `n' as an additional field at the very beginning of each VMT. But it's probably better to let `Size' stay the first field and to save the additional storage for the number of methods in the object. *)
I think it's worth the 4 bytes to have a cleaner structure and not introduce new "dirty tricks" -- just my experience from the way Borland messed up their objects. It also wouldn't be the best promotion for schema types, if this build-in structure, where schema types seem natural, didn't use them.
OTOH, the introduction of object constants would break this structure anyway, since the VMT wouldn't contain only pointers then. So perhaps only declare
Type VmtRec = record Size, NegSize: Integer; MethodsAndConstants: Void; end (* VmtRec *);
and leave all access to "MethodsAndConstants" to the programmer's responsibility (though I don't see any need for this at all -- I think anything can be achieved by assigning the VMT offset to an object, and accessing its fields/methods by normal means).
(BTW: Is "Void" a valid declaration of size 0? Otherwise perhaps "record end", this is allowed at least in BP.)
A "Parent" field in the VMT seems quite useful to me. Also, a list of the childs (this would require a "FirstChild", and a "Sibling" field in each VMT). And perhaps they could be accessed through the VmtRec as well as as normal object constants...
On Mon, 26 May 1997, Frank Heckenbach wrote:
Bottom line: I don't think it can be done all automatically, but it can be done much more comfortably to the programmer than in TV.
I agree with you. I am in effect creating a better way to do it, but it will NOT be in BPCOMPAT. I'm a proficient user of TV, and I'll try to have the object parts of the BPCOMPAT package as compatible with it as possible... It *will* be in a GPC-specific class library of mine I'll contribute to the GPC team "When It's Ready (TM)", of course! ;-)
type MyObject(TObject) Const ObjID:Word = MyBaseID+42; ... End;
or ... why not also
Const ObjID:Word = Inherited ObjID+1;
(Though the simple "+1" might not be good, as it can easily lead to conflicts - but perhaps something similar.)
Concerning the numbering convention, I'd also suggest something "better" than TV's way, perhaps an ID consisting of 4 parts (perhaps 16 bits for each part, this would leave room for a growing number of programs for some time (well - at least as long as 640 KB are enough for everybody... ;-), and the whole thing would be 64 bits (longlongint?)):
- "unique" programmer ID
- ID for a "category" of types (graphical objects, mathematical objects, dialog elements, ...) - unique only for the programmer
- ID for a certain type within a category
- "version number" for the type (i.e., if the fields or their semantics change, the version number will be changed, too, so that old objects in streams can be recognized)
All this is not really needed in a system similar to TV, since you can register the objects yourself in case of conflict, resolving them to your liking. Though it is a nice idea to have a programmer/vendor part... A call to a unit-specific RegisterType procedure could look like this: RegisterObjects(1), the 1 being a value selected by the application programmer to prevent conflicts with another unit. How about a dword, with the first 16 bits for the unit ID and the last 16 bits for the object ID itself within the unit. Gives you 64K units in a single program with 64K objects per unit... Should be quite enough! :-)
The version number isn't a good idea though... The idea with the object ID is to spawn the correct object to load the object from the stream. The correct way to put versioning in your streamable classes is to have a version number stored at the beginning of your object by your Store method, and have your Load method have a 'case' statement that loads the object correctly according to the various versions.
As I said above, I wouldn't like this. But it could be an idea to have ObjID be a string instead of a numerical value. But I'm not sure if it's worth the additional work required...
Not really IMHO... I agree with you on this subject...
Perhaps it's easier to let "ClassName" be a normal object constant (if they'll be implemented). Then the programmer could decide which names to include in the program and how to call them (e.g. the actual identifier might be "TSomething", but for the ClassName it might be preferable to have just "Something").
The idea with having a separate ClassName() function is to let you have a ClassName method, variable or constant without losing the functionality.
The way I see it, the best way is to make a legal way to directly call a specific class constructor. Like the "typed new()" function (passing an object type as the first parameter) does, but being able to put a variable there. Maybe a GPC-specific new() function? With another name of course to avoid incompatibilities...
This would require something like "virtual constructors", wouldn't it? I.e., the constructors would have to have the same parameters in all classes, and their addresses would be stored in the VMT. Doesn't seem impossible, perhaps it's the most logical thing to do.
No... You know that New() works either this way: New(P, Init), where P is a typed pointer (of an object type that has a Init constructor, of course!), where P will be changed to point to the new instance or set as nil if the object didn't construct correctly. The other way is as a function that work like this: P:=New(PObject, Init), which will construct a TObject and return the instance pointer (or nil). It isn't a virtual constructor, you simply choose which object to construct.
We are in part. The primary goal of GPC is to produce the best Pascal compiler the world has ever seen.
I agree with Peter. I, FWIW, didn't use TV -- partly because some parts of it (including class registration) were, IMHO, badly implemented. If gpc's TV would be the same, I probably wouldn't use it, either.
Yes, I agree, but let's keep in mind what we're looking to fix in this thread is the BPCOMPAT package, which will have to look like BP libraries are, at least on the outside. BTW, what didn't you like exactly with TV? As for the class registration, what I didn't like was that it used the data segment, which isn't a very clean hack IMHO... Apart from that, it's ok by me (the class registration system!). The Free method is one of the purest evils in there. :-) (and it is in the BPCOMPAT package!)
So we may need both: a "100% BP compatible" unit for quick changes, and a "better" gpc unit to which programs can be converted afterwards, and which new programs can use from the beginning. Actually, I think, the changes needed for BP programs shouldn't be too big: make the "Load" constructor virtual (if we do it this way), declare the "ObjID" constant, and remove the registration stuff. Could even be simplified with some "IFDEF"s and clever use of the preprocessor, I guess.
I'm developing something here for a "GPC enhanced" class library, I'll keep you posted! Up to now, no compiler changes or new features are needed, except for a clean way to spawn an object from its VMT link. I started developing it with BP so I knew exactly how to do it (it is portable, if you don't count the heck of limitations induced by BP (collections are limited to about 16K elements with BP, where they are "big enough" with GPC) ). I "faked" the 'typed New()' code using Turbo Debugger to learn how it worked...
A "Parent" field in the VMT seems quite useful to me. Also, a list of the childs (this would require a "FirstChild", and a "Sibling" field in each VMT). And perhaps they could be accessed through the VmtRec as well as as normal object constants...
At least Parent would be useful. Note that my class library registration system has a field for the parent object and can walk the registration list to find its childs.
Pierre Phaneuf
"The use of COBOL cripples the mind; its teaching should, therefore, be regarded as a criminal offense." - Edsger W. Dijkstra.