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