On Wed, 4 Jun 1997 13:07:57 +0200 Frank Heckenbach heckenb@mi.uni-erlangen.de 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.
True enough.
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.
Perhaps useless for some purposes - but useful for others. We are all used to data structures with "reserved" members (e.g., the TFileRec and TSearchRec records). The fact is that although "Handle" _may_ be useless for non-windowed objects "SelfID" is quite useful for all sorts of objects - simply because of its function in locating object instances.
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?
Yes.
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.
True - but that would not be the only check.
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;
I did write a similar procedure - basically, it didn't work. The test : "If (p<>Nil)" still returned TRUE. I am still looking into this.
(Better declare it as inline, though...)
Meaning? I hope you are not referring to Turbo's ghastly inline code which has to be commented with ASM statements!
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?
And then, do whatever the message tells you to do with it.
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?
No.
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?
Yes - without using the ghastly DDE stuff.
They could do the same with pointers (even if they can't address what the pointers point to).Or what is it?
Theoretically, it is possible, but it is fraught. First of all, message parameters are passed as Words or Longints. You can typecast :
Longint ( @PMyObject ) or Longint ( PMyObject )
to send the address in a message. When you get it back, it will still be in a type-casted Longint. How do you then do you convert this to an object instance; p := pObject ( aLong ) or p := pObject ( @aLong )
which would you use? then you would do; If p <> Nil then ...
- apart from the fact that I have no idea what all these type casts would be doing to what I am sending and receiving (like I said, I haven't tried sending an object's address, and I have no inclination to try it), personally, I would prefer to send a message parameter as;
PMyObject^.SelfID
and when I receive a message back with the SelfID in "aLong", to do;
p := InstanceFromSelfID ( aLong ) then I would do; If p <> Nil then ...
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.
No. But they did a lot of things in ASM to get round some problems.
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...)-:
No - I was referring to my own OBJECTS.PAS in BPCOMPAT.
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 do!
I don't think one always needs such a "big brother", therefore the ID should not be part of *all* objects.
Perhaps not - but I personally think that one would need it frequently. Once you have it, you wonder how you ever did without it. And what happens when you do need it? What if you suddenly discovered the need to use it in connection with a collection, and a control, and a Stream and a file object? You would need to create a common ancestor for all of them, and then go back and change all your sources to derive from this common ancestor - or you would put it in TObject so as to avoid all that, and break all your code in the process.
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. ... ;-( *)
Ok - you are right ;) - but "for loops" are still easier for some people than linked lists.
I think, everybody who's serious about OOP knows linked lists, don't they?
I knew *about* linked lists for about 7 years but did not know how to implement them. I only gained this knowledge in the past 2 years - and I have been doing Borland-style OOP since 1989. I know some very experienced database programmers who have now moved to Delphi 2 (from VB, FoxPro, Access and Sage Sovereign), who write multi-user and mission-critical database and accounting systems, who do not have (and probably will never have) a clue about linked lists. But they can write "for loops".
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?
I said it was random. I am not saying that anyone would want to do something random like that - but that it could be done. And if you are receiving this number from somewhere else (e.g., another app) then you would need to do something like that.
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),
Yes, you could - and here you wouldn't need the ID.
or by looping through all existing objects.
Looping through all objects with positive IDs - until you find the one you are looking for.
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.
If you already have the pointer, there is no need to do any of this. So, I think you are still missing the point. Your solution involves already having the pointer - but what if you don't already have it? This is what the SelfID retrieves for you. Assuming you receive a message from another application saying "tell the object with ID number 437 to read some data from the serial port" - how would you proceed using your solution?
- 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!
Like I said, destroying an object does not set the pointer thereto to NIL. You would still come up with cases where "If p <> NIL" would return TRUE, and yet, the object does not exist.
(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.)
True. But I am not sure how far this is significant. I once wrote a test program for ChiefAPP (an installation program) with literally hundreds of active objects (buttons, labels, collections, gauges, dialogs, etc), with all sorts of messages going back and forth. On a 486DX2/50, it ran like the wind. If I had 500,000 active objects, then some slowdown might show up - but I doubt that it would be significant. How many instructions are these CPUs supposed to be able to handle per second?
However, in order not to be complacent, perhaps you could work on the OBJECTS unit in BPCOMPAT to change the linked list therein to a more efficient structure? Like I said, I have done my best with it.
Best regards, The Chief Dr Abimbola A. Olowofoyeku (The African Chief, and the Great Elephant) Author of: Chief's Installer Pro v3.50 for Win16 and Win32. Homepage: http://ourworld.compuserve.com/homepages/African_Chief/ E-mail: laa12@cc.keele.ac.uk