OOP_(re)

Wed, 28 May 1997 16:40:06 +0200



Pierre Phaneuf wrote:

> 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! ;-)

Will the gpc class library similar to TV, so TV programs can be ported with
moderate effort, or will it be essentially different?

> 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.

For TV, this is probably ok. However, it might be a good idea to use the
same numbering scheme for bpcompat and the gpc class library (so e.g.
stream files could be used by both libraries, assuming the classes used are
equivalent) - unless bpcompat was so much compatible to TV that they could
use the same stream files.

For a "good" system, I wouldn't like to have to call any registration
procedures manually. And, as I showed, this isn't necessary if "ObjID"
could be declared with the classes.

> 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! :-)

With my version, it would be 64K units "all over the world", if the IDs
were fixed in the units. Of course, one could think of a way to partition
32 bit that would suffice for awhile. But, OTOH, I don't like the
possibility of creating limits that could be reached in some years.
Would an ID of 8 bytes matter, i.e. are there any stream files where
4 additional bytes per object would matter?

> 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.

Probably a matter of taste. With the way you described, each class that
exists in several versions has to do the work of loading the version and
selecting the case by itself. I thought part of this work could be done
by the stream load routines in one place. Of course, I didn't work this
out completely, but generally I think each class should have to do as
little work as possible (e.g. being registered, doing additional work for
loading). However, if this additional work can be done in a base class
and just be inherited for most "normal" classes, this would be ok to me.

(Keep in mind that I'm not talking about bpcompat, but about a "good" class
library that should do things the best way we can think of, regardless how
Borland did things. Borland's way could only be decisive between some
alternatives that are equally good otherwise.)

> > 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.

If the type is known at compile time (as is always the case in BP), the
compiler knows which constructor to call. But if you want to pass a
variable VMT link to "New", the runtime system needs a way to find out the
address of the actual constructor. AFAICS, this can only be done by
storing the constructor's address in the VMT, that's what I meant by
a "virtual constructor" (and why shouldn't it be actually declared as
"constructor ... virtual;"?).

The stream load method should just make sure that the read VMT actually
belongs to a descendant of TObject, e.g. by checking if "p^ IS TObject"
after assigning "TypeOf(p^):=VMT". BTW: Is "IS" already implemented in gpc?

> 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?

Several things. E.g., as I said above, the "administrative work" that the
programmer has to do. Since Borland wrote the compiler, they could have
simplified this very much. For me, when writing "basic units", one of the
main goals should always be to leave as little work to the programmer that
uses the units as possible.

Then, their massive use of assembler (partly for questionable efficiency
matters, partly because Pascal didn't support some things that were needed
- and again, they wrote the compiler, so they could have created a Pascal
way to do things).

And, more generally, I don't like the message-driven concept too much.
I prefer things like "readkey" and so - of course, more advanced than in
BP's Crt unit. And complex programs (with a look like TV) can be
programmed in such a way - I know this because that's what I do in BP.

The African Chief wrote:

> The TObject type as I implemented it in OBJECTS.PAS has
> a number of fields;
> 	"Parent" and "Child" (each declared as a "pObject")
> 	"Handle" and "SelfID" (each declared as an Integer)
> 	"Name" (declared as a String[64])
> 	"SelfPtr" (private field declared as a "pObject")
>
> At the moment, "Parent" and "Child" do nothing, [...]
> "SelfID" is an integer which holds a unique numeric ID for each
> living TObject descendant.

AFAICS, they will hold the parent/child *objects*, not *classes*, and
the ID of instances. Also useful, I think, but not to be confused with
the parent/child classes, and "ObjID" (= class IDs) which should resonably
be in the VMT, since they depend only on the class, not on the instance.

BTW: If objects constants and/or class variables are introduced, the
name "VMT" will not be very appropriate then...

> Can we make TObject automatically become the ancestor of each
> Object type (i.e., any object which is just defined as "Object")?;
>  if so, is this desirable?;

No. There are applications of OOP that are quite different from the class
libraries as TVL, OWL and so on. Sometimes I use objects as "convenient
records" without any need for inheritance, virtual methods or streams.

> if so, can this solve any of the current
> problems with Streams and stuff?

AFAICS, objects that are stored in streams, must be descendants of TObject.
But not all types of objects need to be able to be used in streams at all.
I think the programmer should be allowed to decide for himself if he wants
his objects to be "streamable" (by making them descandants of TObject or
its descendants) or not.

BTW: Making TObject the ancestor of all objects automatically would not be
BP compatible...

> I would have thought that some of the fields declared in TObject could
> be put to better use than I have done so far - and that more fields could
> be added to TObject, to cater for other things (i.e., if TObject will become
> the ultimate ancestor of all Object types).

No objections from me. Perhaps, in some time, most object types written
will be descendants of TObject, that's ok. I just don't think this should
be forced.

BTW: Would it be possible at all? Doesn't TObject refer to TStream? So if
TObject was a part of the "core language", also TStream would have to be.
And I don't think that would be a good idea. IMHO, such things belong in
basic units that can be replaced and/or modified without changes to the
compiler.

Peter Gerwinski wrote:

> BTW, somebody knows a reasonable way how to implement multiple inheritance?

I once did a comparison of several languages' concepts some years ago.
One of the results were that each method leaves some problems open. :-(

However, Java's way with single inheritance and "interfaces" didn't seem
too bad to me (though I haven't programmed in Java). I think it could be
implemented with reasonable effort, so I'd favour this way.

If, however, "real" multiple inheritance is wanted for some reason (though
I don't know any applications that really demand it), perhaps do it like
C++ (I don't remember the details of C++ now, but it might be a good idea
to not introduce yet another concept)...

> True for a Program, false for a Unit.  While the GPC compiler is *much*
> smarter than BP and would remove unused code and variables, the GNU linker
> is not able to remove dead code, it only does so if a whole Unit is unused.

I suppose there's not much hope of getting a smarter linker in the near
future, is there?

The African Chief wrote:

> Yes (but Handle is present in Delphi as well). However, the main reason
> for keeping it is because the OBJECTS unit exports a function which returns
> a pObject, which can be used to locate an object instance by its Handle
> (i.e., given any Handle, you would thereby be able to locate its owner). 
 
I don't know all the details here, but isn't it possible to have a
TWindow(TObject) with Handle being a field of TWindow, and this function
returning a PWindow? This would seem natural to me. Otherwise, this might
be an application for an interface (consisting of only GetHandle, perhaps).

> This is not an academic issue, because I have seen the utility of 
> this facility in other contexts (viz - my portable "CHIEFAPP" class
> library, for BP 7.x, Delphi1 and Delphi2, from which project this
> OBJECTS unit was borrowed and cannibalised). Thus, the fields
> need to be in the ultimate "mother of all objects".

Similar arguments might be applied to many kinds of fields, with the
possible danger of blowing up the "mother of all objects" very much.
Then it could - at best - be the mother of all objects in a particular
class library, but it wouldn't promote the goal of making it a widely
used base class.

> It has a present use - to be able to locate every object instance by its 
> own unique ID. See above.

I'm probably missing something - but isn't the address of an object a
"unique ID"? (One that makes it very easy to "locate" the object... ;-)

Peter Gerwinski wrote:

>   * There are few places in my planned object hierarchy (I am *also*
>     working on a completely new library;) where MI could be useful:
>     There are several independent branches of recursive objects, say
>     `VisibleObj' and `DataObj', which all have a `Child ( i: Integer )'
>     method returning the `i'th child.  It would be nice to implement
>     some stuff, say a `ForEach' method, in a common ancestor.  Without
>     MI, this common ancestor must be a *very* "low" object, `BaseObj'
>     itself, the parent of all objects.  I am not sure if it is wise to
>     make *every* object in the hierarchy potentially recursive.

Also looks like an application for interfaces...

>  (* And: C++ has it, and we must demonstrate that GPC is not inferior. ;*)

Blindly copying useless features doesn't exactly prove superiority...
(I'm not claiming MI is useless for now, but I've yet to see an example that
really justifies the problems.)
-- 
Frank Heckenbach, Erlangen, Germany
heckenb@mi.uni-erlangen.de
Turbo Pascal:   http://www.mi.uni-erlangen.de/~heckenb/programs.htm
Internet links: http://www.mi.uni-erlangen.de/~heckenb/links.htm


Frank Heckenbach (heckenb@mi.uni-erlangen.de)

HTML conversion by Lluís de Yzaguirre i Maura
Institut de Lingüística Aplicada - Universitat "Pompeu Fabra"
e-mail: de_yza@upf.es