[comp.sys.mac.programmer] List manager and Think C

jamesm@sco.COM (James M. Moore) (08/30/89)

One question about lists, one comment about the Think object
libraries:

1.  Is there any way to change the location of a list?  There doesn't
seem to be a list manager call to do so.  The kludge that I tried that
didn't work was to directly modify (**listHandle).rView, and then
InvalRect the entire list.  If this isn't possible, how do you clone a
list?  It seems like you could just build a new list that's the same
size as the old, then just set (**newListHandle).cells to point to the
old list's data.  The first problem I see with that is that when you
delete the old list, it expects to be able to delete its data.  I
guess you could set the cells handle to a dummy handle that it could
then delete, but this seems to be moving from the realm of 'hack' into
that of 'kludge.' Anyone have a more elegant way to do this?

2.  The reason the list question comes up is that I'm building a list
class as a subclass of CPane, and I'd like to implement Offset(). My
class is practically done, but I spent a considerable amount of time
working around having the origin reset every time I went into a
drawing routine.  This morning, I realized that I would probably have
been better off if I had started out with a CControl superclass, as I
wouldn't have to deal with any of the Prepare() stuff that CPane does
for you.  Think 4.0 is great fun, even though it doesn't have many of
C++'s features (what I really want are virtual functions*, although
everything else would be nice).  

*virtual functions:  I understand this to mean that you can have a
single function defined multiple times:

	foo(int x);
	foo(long x);

and the function that gets called depends on the arguements that you
pass.  Feel free to correct me if I'm getting the terminology wrong.
-- 
James Moore                            | You will face many tests in life.
Santa Cruz Operation UNIX Tech Support |
jamesm@sco.com                         | This is not one of them.

pem@cadnetix.COM (Paul Meyer) (09/01/89)

In article <5501@viscous.sco.COM> jamesm@sco.COM (James M. Moore) writes:
>*virtual functions:  I understand this to mean that you can have a
>single function defined multiple times:
>
>	foo(int x);
>	foo(long x);
>
>and the function that gets called depends on the arguements that you
>pass.  Feel free to correct me if I'm getting the terminology wrong.

	No, "virtual functions" refer to dynamic binding.  TC4 ONLY offers
dynamic binding, not static binding.

	What you refer to is called function overloading.  It is one of
the very niftiest features of C++, but is also a major hassle to implement
debuggers and things around.  I'm not really surprised that it was not done
in the first release of object C, but I join many others in hoping that
function and operator overloading will be supported soon.

	The difference between dynamic and static binding is whether inher-
itance is resolved at compile time or run time.  For example, take this
class heirarchy: (C++ features I don't think are in TC4 in square brackets--
I haven't received my upgrade yet :-()

	struct FOO {
		int		myvar;
		[virtual ]int	myfunc(void);
	};

	struct BAR : [public] FOO {
		long		my_other_var;
		[virtual ]int	myfunc(void);
	};

	FOO::myfunc(void) {
		return 1;
	}

	BAR::myfunc(void) {
		return 0;
	}

	FOO	foo1;
	BAR	bar1;

	main()
	{
		FOO	*foo2;

		foo2 = &foo1;
		if (foo2->myfunc())
			printf("myfunc(foo1).\n");
		else
			printf("!myfunc(foo1)!\n");
		
		foo2 = &bar1; /* this is valid because a BAR is a FOO */
		if (foo2->myfunc())
			printf("myfunc(bar1)!\n");
		else
			printf("!myfunc(bar1).\n");
	}


	In the case of dynamic binding, TC4 or C++ including the "virtual"
keyword where I indicated it, the output will be

myfunc(foo1).
!myfunc(bar1).

because the type of the thing pointed to by foo2 is checked at run-time.
In the case of static binding, not available in TC4, the output will be

myfunc(foo1).
myfunc(bar1)!

because at compile time, foo2 is declared to point to a FOO, not a BAR.
In TC, if you want to get the effect of static binding, you should be able
to use the scope-resolution operator to explicitly say you want FOO's myfunc,
not the myfunc belonging to the exact class of the instance you are pointing
at, by saying
	... if (foo2->FOO:myfunc())...
so not allowing static binding doesn't lose any functionality.  It DOES, how-
ever, lose efficiency in the case where static binding would be sufficient.
Every object must have a way to tell its exact type at runtime, to specify
what functions it changes relative to superclasses.  This is normally done
by putting a pointer to a table of function addresses in the object, with a
separate table for each type, and dereferencing into it to make member function
calls.  Using this scheme, extra memory is used for the function table (and
since this is a Mac, probably a master pointer for it) and the pointer in each
object; extra time is used to perform a (double on the Mac) dereference for
each member function call.


Paul Meyer                      pem@cadnetix.COM
Daisy/Cadnetix Inc. (DAZIX)	{uunet,boulder}!cadnetix!pem
5775 Flatirons Pkwy.            GEnie P.MEYER
Boulder, CO 80301               (303)444-8075x277

ericsc@microsoft.UUCP (Eric Schlegel) (09/01/89)

In article <5501@viscous.sco.COM> jamesm@sco.COM (James M. Moore) writes:
>One question about lists:
>
>1.  Is there any way to change the location of a list?  There doesn't
>seem to be a list manager call to do so.

The code that I came up with to do this:


#define dxyScroll	(15)	// scroll bar width

#define	DxRc(rc)	((rc).right - (rc).left)
#define	DyRc(rc)	((rc).bottom - (rc).top)

// one of the instance variables of the LBOX class is
//	ListHandle	lh;

// SetRc(rcNew): set the location of the listbox
// to be rcNew. RcNew INCLUDES the scroll bars; that is,
// the list box inset from the sides of rcNew by the
// width of a scroll bar.
void LBOX::SetRc(Rect rcNew)
	{
	short		dx;
	short		dy;
	RgnHandle	rgn;
	Rect		rctT;

	/* by the way, everything between here and the		*/
	/* LSize is completely unsanctioned by Apple.		*/
	/* there is no List Manager interface to move the	*/
	/* top left corner of a list.				*/

	SetRect(&rctT, (*lh)->rView.left, (*lh)->rView.top,
		(*lh)->rView.right + dxyScroll, (*lh)->rView.bottom);
	EraseRect(&rctT);
	
	SetRect(&rctT, 0, 0, 0, 0);
	rgn = NewRgn();
	GetClip(rgn);
	ClipRect(&rctT);

	dx = rcNew.xLeft - (*lh)->rView.left;
	dy = rcNew.yTop - (*lh)->rView.top;

	OffsetRect(&((*lh)->rView), dx, dy);
	(*lh)->clikLoc.h += dx;
	(*lh)->clikLoc.v += dy;
	if ((*lh)->vScroll != NULL)
		MoveControl((*lh)->vScroll, dx, dy);
	if ((*lh)->hScroll != NULL)
		MoveControl((*lh)->hScroll, dx, dy);

	SetClip(rgn);
	DisposeRgn(rgn);

	LSize(DxRc(rcNew) - dxyScroll, DyRc(rcNew) - dxyScroll, lh);
	}

Eric Schlegel
----
Microsoft owns my code, but the opinions are my own.

svc@well.UUCP (Leonard Rosenthol) (09/02/89)

In article <5501@viscous.sco.COM> jamesm@sco.COM (James M. Moore) writes:
>One question about lists, one comment about the Think object
>libraries:
>
>1.  Is there any way to change the location of a list?  There doesn't
>seem to be a list manager call to do so.  The kludge that I tried that
>didn't work was to directly modify (**listHandle).rView, and then
>InvalRect the entire list.  If this isn't possible, how do you clone a
>list?  It seems like you could just build a new list that's the same
>size as the old, then just set (**newListHandle).cells to point to the
>old list's data.  The first problem I see with that is that when you
>delete the old list, it expects to be able to delete its data.  I
>guess you could set the cells handle to a dummy handle that it could
>then delete, but this seems to be moving from the realm of 'hack' into
>that of 'kludge.' Anyone have a more elegant way to do this?
>
	Here is some code (yes, I know it's in Pascal, but the Pascal code was
closer..) that will Hide and Show LM Lists.  You can easily modify it to simply
offset lists using the same techniques.

PROCEDURE HideDLOGList(theDialog: DialogPtr;
                       theItem: integer;
                       theList: ListHandle);

  VAR
    theType: integer;
    theHandle: handle;
    theRect: Rect;

  BEGIN
    IF theList^^.rView.left < 1000 THEN {Only if not already hidden!}
      BEGIN
        GetDItem(theDialog, theItem, theType, theHandle, theRect);
        HideDItem(theDialog, theItem);
        OffsetRect(theList^^.rView, 16834, 0);
        OffsetRect(theList^^.vScroll^^.contrlRect, 16834, 0);
        HiliteControl(theList^^.vScroll, 255); {Disable it too...}

        InsetRect(theRect, - 1, - 1);
        InvalRect(theRect);
      END;
  END;

PROCEDURE ShowDLOGList(theDialog: DialogPtr;
                       theItem: integer;
                       theList: ListHandle);

  VAR
    theType: integer;
    theHandle: handle;
    theRect: Rect;

  BEGIN
    IF theList^^.rView.left > 1000 THEN {Only if not already shown!}
      BEGIN
        ShowDItem(theDialog, theItem);
        GetDItem(theDialog, theItem, theType, theHandle, theRect);
        OffsetRect(theList^^.rView, - 16834, 0); {Restore based on Show}
        OffsetRect(theList^^.vScroll^^.contrlRect, -16834, 0);
        HiliteControl(theList^^.vScroll, 0); {and enable it!}
        DrawADLOGList(theDialog, theList);

        InsetRect(theRect, - 1, - 1);
        InvalRect(theRect);
      END;
  END;

{DrawADLOGList is simply a routine which updates a list in a dialog}

Hope that helps...

-- 
+--------------------------------------------------+
Leonard Rosenthol        |  GEnie : MACgician
Lazerware, inc.          |  MacNet: MACgician
UUCP: svc@well.UUCP      |  ALink : D0025

duggie@Jessica.stanford.edu (Doug Felt) (09/02/89)

In article <9389@cadnetix.COM> pem@cadnetix.COM (Paul Meyer) writes:

>	...  TC4 ONLY offers
>dynamic binding, not static binding.
>
    [description of function overloading omitted]
>
>	The difference between dynamic and static binding is whether inher-
>itance is resolved at compile time or run time.

These statements taken together would seem to imply that TC4 always
goes through the message dispatcher.  Instead, the linker (or
compiler, it's difficult to tell with TC) resolves all the messages it
can into procedure calls.  This turns out to be quite a lot-- all
messages to "inherited" methods (please note I take no position on the
use of this keyword), and all messages with only one implementation,
can be resolved to function calls.  The optimized compile and link in
Apple's Object Pascal does this, among other things.

     [good example of the behavior of virtual functions omitted]

>	... if (foo2->FOO:myfunc())...
>so not allowing static binding doesn't lose any functionality.  It DOES, how-
>ever, lose efficiency in the case where static binding would be sufficient.

Because of this optimizing behavior, I imagine calls such as the one
proposed would be optimized to direct function calls, with no
performance penalty.  I don't have access to the new compiler at work
so haven't tested this.

There are other optimizations that Apple's compiler provides which
may or may not be in TC4.  One is inverting the message tables.  The
idea here is that objects have more messages than messages have objects.
Doing a message lookup on an object is slow, because you may have to
search all of its methods before finding out that the method is 
inherited, and then have to search its superclass's table, etc.  With
inverted tables, you have a table based on the message listing those
classes which implement a method for that message.  Often you only
have to search one or two entries before getting a hit.

Another optimization is caching the last dispatch.  Basically, each
time you get a hit you copy the hit to the a special position in the
table which you always test first.  If a message is mostly going to
the same class then it will hit on the first try.  If inherited
messaging bypasses the dispatch then this cache should be good a lot
of the time, since you rarely message instances of higher-level
classes.

I believe most C++ implementations get around these two by building an
entire dispatch table for each class and indexing into it.  I don't
think TC4 does this.  It seems to index its object descriptions and
method tables off of A5, using object-based dispatch tables and no
cache.  This is probably not so bad since resolving message dispatches
into function calls gets rid of the lion's share of the message tables
and dispatch overhead.

I'm still curious to know what the limits are on the number and sizes
of objects, the number of methods, and so on.  I haven't been able
to find any mention of this, or for that matter, the class 
description and dispatch mechanism, in the manuals.  Perhaps Rich
can help us out.

>Paul Meyer                      pem@cadnetix.COM
>Daisy/Cadnetix Inc. (DAZIX)	{uunet,boulder}!cadnetix!pem
>5775 Flatirons Pkwy.            GEnie P.MEYER
>Boulder, CO 80301               (303)444-8075x277

Doug Felt
Courseware Authoring Tools Project
duggie@jessica.stanford.edu

ts@cup.portal.com (Tim W Smith) (09/03/89)

The method dispatching is different when you run the program from Think C
that it is when you build an application.  When you run from Think C it
does not appear to optimize method dispatching.  I noticed this because I
wanted to see how much overhead was involved in OO stuff in Think C, so
I wrote a test program and single stepped it with TMON.  Everything wemt
through the method dispatcher.  I then build the application and single
stepped.  Now many things did not go through the method dispatcher.  Instead
they jsr'ed to a jmp to the method.

						Tim Smith