OOP_(re)

Wed, 4 Jun 1997 13:07:57 +0200



The African Chief wrote:

> >We're talking about a class library for widespread use, aren't we?
> >Imagine if everybody changed the base objects: No units of different sources
> >would fit together!
>
> No, you shouldn't - IF you are going to distribute your own
> sources for public consumption. If it is just for your own
> private use, I don't see the problem.

A problem is there if you might want to use someone else's sources based on
the original library some time. Otherwise, you're right.

> >The OOP way to do this is: if something doesn't suit you, derive a new class,
> >and apply all modifications you want to the new class.
> 
> That is still based on the assumption that you are happy to live with 
> the original class as it was. What if you are not? It is like having a bad 
> ancestor with bad genes or no good genes at all. I can see the point
> in the "pure OOP" theory, and I would do that if I was presented with a
> ready-made ancestor (you can't choose your ancestors, or the genes
> which you will inherit or not inherit from them). However, if I were the 
> one to decide what that ancestor should be, I would still do it exactly 
> like I did it.

That's exactly my point. That's why I'm so picky about the base classes.
Look at it this way: If there's a field in the base class that not everybody
needs, there's no way for those who don't need it to remove it by deriving
a new class, so they'll have to carry something useless in their programs.

If, however, a field that some people need is not in the base class, they can
just derive their own base class and forget about the old base class.

This is from the point of view that there are common base classes (though
not enforced by the compiler, this will happen if many people use a library
and write new code for it).

> I may have other uses for it in TObject - and now I do. AFAICS when you
> dispose of an object and/or call its destructor, GPC does not set it to NIL.

By "it", you mean a pointer to it, do you?

> This has caused me a number of problems in trying to ascertain which
> instance has been destroyed (you can't just check by finding if its value
> is NIL). A temporary hack (until I can see a better alternative) has been
> to set the handle to a specific negative number in the destructor (the
> handle should normally never be negative).

Doesn't work reliably. Once the object has been disposed of, its memory can
be overwritte by other things, so in the place where the handle was there
can be a positive number.

Why don't you just manually set the pointer to NIL? You can use this
generalized procedure:

procedure Destroy(var p:PObject);
begin
  Dispose(p,Done);
  p:=NIL
end;

(Better declare it as inline, though...)

> >So the ID can be a pointer! Then InstanceFromSelfID would be a simple
> >lookup in an array (or whatever structure is used to hold the IDs).
>
> It is not an array. Even if it were, you are still missing the point. The point
> is that you can locate that pointer even if you didn't know whether the object
> instance actually exists or not.

Perhaps it's the same thing Peter mentioned. See my reply ("aliasing bug").

> >>     If Assigned ( p ) then begin
> >>        If p^.Name = 'CHIEFDIALOG' then { blah blah }
> >
> >better: If Typeof(p^) = Typeof(TChiefDialog) then { blah blah }
> 
> Perhaps.
> 
> >Removes the need for Name field, also pointer comparisons are (usually)
> >faster than string comparisons.
> 
> Can you write "TypeOf" to a file or to a Stream?

No (well, you could, but I'd be rather useless). For this, there's the ObjID.

You could also use the ObjID in the comparison above, but I don't see any
advantages, but the disadvantage that not all classes will have an ObjID.
You could also (as soon as it's implemented) use the "IS" operator, which
would not only recognize TChiefDialog, but all descendants of TChiefDialog.

> ChiefObject?

Your choice!

> Big problem with pointers. Try passing a pObject or whatever
> from one application to another, with a call to PostMessage
> (I haven't, but I personally wouldn't try it).

Hmmm... -- and what would the other application do with an ID? As far as
you've shown so far, you use the IDs to find a pointer, and then?

I don't see a need for IDs here -- but even if there is, this certainly
applies to a very special kind of objects, not to a general TObject.

> They don't need to reside in any kind of shared memory. I have
> passed messages from Win32 applications and DLLs to other
> Win32 applications (and even Win16 applications, and a CMD.EXE
> command prompt), all of which live in separate address spaces,
> with a call to PostMessage.

So the different apps can't address each other's objects, can they?
Then what do they do with the IDs (I really don't understand)? Do they just
store them and send them back in some other messages? They could do the same
with pointers (even if they can't address what the pointers point to).
Or what is it?

> >Yes, you're right. With Load being a constructor, and explicitly declaring
> >the StreamRecs, this works. BTW: Does TObject need a Load constructor at all?
>
> Probably. I seem to remember needing it there to implement my Streams.

I don't know. I just know Borland's TObject doesn't have Load. OTOH, if
Load is going to be virtual, it must be put in some base class (or base
interface like streamable).

> >I suppose you're looping through an array of IDs, right?
>
> No. You are dealing with data held in a list object (a TObject
> descendant). Have you actually looked at OBJECTS.PAS?

Parts of it (I don't like reading uncommented asm code very much...)-:

Where exactly should I look (class.method)? The string "id" doesn't appear
anywhere in objects.pas, and the string "handle" only for DOS file handles
and EMS handles, and the only places where "list" appears are
"TMemoryStream.SegList/.ChangeListSize", "TStringList" (irrelevant here)
and "TItemList" -- an array of pointers(!).

> >What do you mean by "allocate"? Do you have a global "collection" of all
> >"active" (=existing?) objects, with their addresses and IDs?
>
> An object declared in the implementation section, with a linked list
> of existing object instances.

Perhaps you need them. I don't think one always needs such a "big brother",
therefore the ID should not be part of *all* objects.

> >I still don't see any problem. Can't you just remove any "ID" fields (that
> >contain the ID of the object itself), and change any references to other
> >obejcts' IDs into pointers to them? The "collection" above would then contain
> >all the addresses of active objects, and you could easily loop through them.
>
> You could walk the list, yes. However, not everybody is comfortable
> with linked lists - but AFAIK, everybody can write a "for loop".

(* Everybody? -- Just look at c.l.p.b. ... ;-( *)

I think, everybody who's serious about OOP knows linked lists, don't they?

> >> So, I could really just do something randomly like this;
> >>
> >>    p := InstanceFromSelfID ( 5 );
> >
> >This means "give me a pointer to the object with ID 5", or what?
>
> Yes - when you don't even know whether such an object exists.
> 
> >With the changes above, you would already have the pointer instead of the
> >ID 5, so this function call would even get superfluous.
> 
> You would only have the pointer by walking the list and incrementing a 
> counter until it got to 5, and then retrieving whatever was being pointed
> to when you get to 5. Doesn't save you any coding at all.

What I meant, where do you get the "5" from? It's not hardcoded in any
program, is it? AFAICS, you got this number either as an ID of a specific
object (of which you could just as well have got the address in the first
place), or by looping through all existing objects.

Well, I see two problems addressed by you:

- to check if a given ID is valid, and if so, give a pointer to the object.
  This is almost trivial with pointers as "IDs": just check if the pointer
  is in the list, if so return it, otherwise return NIL.

- to do something for all objects.
  Just walk through the list. BTW, it's more efficient than for-looping
  through all IDs, because you don't hit the "dead" IDs. Furthermore, it's
  much more efficient (O(n) instead of O(n^2)) because you don't have to
  look up every ID by searching through the list!
  It might be a little harder to code (and really just a little), but
  firstly, this could go into a standard routine, written once, and
  secondly, this can't seriously be a reason for "blowing up" all objects
  and slowing down some things (including this problem itself!).

(Apart from this discussion, a simple linked lists is quite an inefficient
thing for this use (regardless whether with numeric IDs or pointers) -- as
soon as the total number of objects gets big, searching the list gets slow
-- other structures to be considered include trees and hash tables.)

> However, the mechanism
> that it presents for inter and intra process communication is simple enough
> to grasp, and lots of programmers know it - and, more importantly, it is the
> only one that I know. So everything I do with my classlib will be influenced
> by that mentality, especially since it was written solely for use with the Win16
> and Win32 APIs. I presume that CYGWIN provides these facilities as well
> (which it would need to do to support the Win32 API). I also believe that the
> OS/2 PM APIs provide a similar messaging system.  Perhaps being too
> influenced by this scheme is a handicap. However, I do not see that we can
> ignore it completely. As someone who has tried to write a Windows class library
> that makes no call to any other class library, my method of implementation was
> what worked best for me. I cannot say that it is the only, or "the best" or "the
> purest OOP" method. In fact, I was not concerned with such issues at all.
> However, I took a pragmatic approach, it worked, and it worked beautifully.
> Perhaps I should have done my GPC objects unit in a different way - but I doubt it.
> The only reason I did it at all was because I already had much of the code written
> elsewhere. I only had to amend it to compile under GPC. Like I said, BPCOMPAT.1.0
> is just a beginning. The code is now out there. These  discusssions have been
> interesting - and it shows that the old saying about lawyers ("two lawyers, three
> opinions") is also true for programmers :-). However, if anyone is going to take
> objects.pas to greater heights, it won't be me. I have already done the best 
> I can do. 

That's ok! Note that I'm not talking about the BPCompat package (actually I'm
not interested in its OOP/TV part very much because I have no use for it and
I don't know so much about BP's TV). I'm rather talking about designing a
"good" gpc class library -- "good" in the sense that it's not aimed or
mostly influenced by one specific platform, that it can be used for many
different kinds of programs, and that is it "good" Pascal (I don't mean
only Standard Pascal, but Pascalish things rather than quick hacks,
inefficient code in important places, avoidable type casts or even
assembler).
-- 
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