[comp.sys.mac.programmer] CDEF Woes...

spencer@cgrg.ohio-state.edu (Stephen N. Spencer) (02/26/91)

I've been experimenting with writing CDEF (Control Definitions) for a few
custom-type controls (an ICON button, a floating-point slider), and have
run into a problem.  

First, the setup:  Since I want debugging messages from the CDEF, I'm not 
compiling it as a CODE resource and linking it into my tester application.
(I have two windows in my tester app: a drawing window, and a TextEdit window.
I have an 'AddMessage()' function which takes a character string and appends
it to the TextEdit window -- instant scrolling debugging message window!)

I'm doing the following to associate my control with the control handle:
First, I make the window, set the port, and call my setup function:

	gDrawWindow = NewWindow(....blah blah....);
	SetPort(gDrawWindow);
	SetupCDEF(gDrawWindow);

Here's the setup function:

SetupCDEF(win)
	WindowPtr win;
{
	Rect r;
	Handle h;

	h = NewHandle(h);
	*h = (Ptr)MyControl;   /* MyControl() is my CDEF 'main' function. */
	SetRect(&r, 160, 30, 400, 60);
	myCH = NewControl(win, &r, "\p", TRUE, 0, 50, 100, scrollBarProc, 0L);

	HLock(myCH);
	(*myCH)->contrlDefProc = myH;
	HUnlock(myCH);
}

This works, to a point.  'MyControl' intercepts all 'drawCntl' and 'testCntl'
messages passed through the control handle 'myCH', I'm glad to report, but
where it messes up is that I don't get any 'initCntl' message(s) at all.

I could, of course, compile this CDEF into a stand-alone CODE resource and 
use ResEdit to paste it into the test application, making the requisite changes
to the 'NewControl' statement, (I know how to do that).

I don't want to, however.  I want to see debugging messages.  Is there SOME
way to link a CDEF function to a control handle so that it can be compiled in
together with the testing application? 

Thanks in advance for any help anyone out there can provide.  When these CDEFs
are finished, I will post them to the 'net:  I'm sure they would be useful.

Stephen N. Spencer     |"Now you must dance the dance that you imply!
ACCAD   (614) 292-3416 | Your actions will follow you full circle round,
The Ohio State Univ.   | The higher the leap, I said, 
1224 Kinnear Road      | The harder the ground!"
Columbus, OH 43212-1163|		- Amy Ray, "Center Stage"
spencer@cgrg.ohio-state.edu||71160.3141@compuserve.com||stephen_spencer@osu.edu

hawley@adobe.COM (Steve Hawley) (02/27/91)

In article <1435@gertie.osc.edu> spencer@cgrg.ohio-state.edu (Stephen N. Spencer) writes:
>SetupCDEF(win)
>	WindowPtr win;
>{
>	Rect r;
>	Handle h;
>
>	h = NewHandle(h);
>	*h = (Ptr)MyControl;   /* MyControl() is my CDEF 'main' function. */
>	SetRect(&r, 160, 30, 400, 60);
>	myCH = NewControl(win, &r, "\p", TRUE, 0, 50, 100, scrollBarProc, 0L);
>
>	HLock(myCH);
>	(*myCH)->contrlDefProc = myH;
>	HUnlock(myCH);
>}
>
>This works, to a point.  'MyControl' intercepts all 'drawCntl' and 'testCntl'
>messages passed through the control handle 'myCH', I'm glad to report, but
>where it messes up is that I don't get any 'initCntl' message(s) at all.

OK - the reason you aren't seeing the initCntl messages is because they get
called at NewControl() time --before you have installed your procPtr into the
contrlDefProc.  This isn't surprising.

BUT
Beware! h = NewHandle(h); is dangerous.  You are taking an uninitialized local
variable and using its contents as the size argument to NewHandle.  That's the
same as doing this:

myFunc()
{
	int k;
	char *p;

	p = NewPtr(k); /* k is not initialized */
}

Thta's not the end of your trouble.  Suppose you do this correctly:

h = NewHandle(SOMESIZE);

*h = aFunctionPointer;

This is also bad.  You see, h is a handle to moveable memory.  This means
that it is a pointer to a master pointer which then points to memory gifted
you you by the memory manager.  By assigning a value to *h, you are wiping
over the master pointer value and effectively losing the memory.  What's
worse is that aFunctionPointer is now going to be treated as a master pointer
to relocatable memory by subsequent memory manager calls.  Most likely, you
are safe, as the worst that will happen is an HLock() or HUnlock(), but don't
expect this to work.

Another problem:
Think C pulls some acrobatics to allow you to declare global variables and
static arrays in CDEFs (and other stand-alone code segments).  In order for
them to work (correct me if I'm wrong, Rich), you'll need a call to SetUpA5()
and RestoreA5() in your main to allow you to access your variables.  This
means that your code has to be different for the final version than it does
for the debugging version -- different enough to be cause for worry.

I've written a bunch of CDEFs (mostly buttons -- one of them used to be at
sumex in source form as an example) and what I have done to debug them was
first write a shell application that creates a type of control that is
closest to what I'm creating (ie, button, scroll, etc) and exercises every
aspect of it.  When that's done, I add some code to add a new file to the
resource search path of the application (say add in blah.CDEF) and change
the NewControl to grab a CDEF of the an ID and type that will match what I
will create.  I then build an application and leave it.

Next, I build the CDEF and have think C write the output into blah.CDEF, and
I can then use the application to test it.  I find this works best since I
will be spending most of the time in Think C on the CDEF not the test
application.

The bad news is that debugging is difficult.  You can be clever though, but
using the refCon field of the control (which is defined for program use, so
it's safe for you to dick around with for debugging) and do something like
makeing the refCon be a pointer to your debugging output.

Then you can make your CDEF look like this

#ifdef DEBUG
typedef void (*PFV)(); /* pointer to function returning void */
#define DebugMessage(ctrl, s) \
	(*((PFV)((**ctrl).refCon)))(s) /* call the debugging output */
#endif DEBUG

pascal short
main(ctrl, message) /* I forget the args -- help me out here */
ControlHandle ctrl; long message;
{
	SetUpA5();
#ifdef DEBUG
	DebugMessage("Entered.  Message is: ");
	/* code to do a NumToString, etc etc etc */
#endif DEBUG
	RestoreA5();
}

Now maybe you intend to use the refCon for your own insidious needs in the
control.  That's okay.  You can always play the dereference game and pretend
that the refCon is really a pointer to the following data structure:

typdef struct {
	long realRefCon;
	PFV debugRoutine;
} InsidiousRefcon;

Then in your shell program you can do this:
InsidiousRefcon ir;

main()
{
	ControlHandle ch;

	ir.realRefCon = WHAT_I_REALLY_INTENDED_AS_THE_REFCON;
	ir.debigRoutine = myDebugOutputFunction;
	ch = NewControl(...... , (long)(&ir));
}

Then you might have your control do something like:

#ifdef DEBUG
#define RefCon(ch) \
	(((InsidiousRefcon *)(**ch).refCon)->realRefCon)
#define DebugMessage(ctrl, s) \
	*(((InsidiousRefcon *)(**ctrl).refCon)->debugRoutine)(s)
#else DEBUG
#define RefCon(ch) \
	((**ch).refCon)
#endif DEBUG

I may have botched all the parenthesis twiddling, but you get the idea.

Good luck.

Stevge Hawley
hawley@adobe.com
-- 
"Did you know that a cow was *MURDERED* to make that jacket?"
"Yes.    I didn't think there were any witnesses, so I guess I'll have to kill
 you too." -Jake Johansen

jwinterm@jarthur.Claremont.EDU (Jim Wintermyre) (02/27/91)

In article <1435@gertie.osc.edu> spencer@cgrg.ohio-state.edu (Stephen N. Spencer) writes:
>I've been experimenting with writing CDEF (Control Definitions) for a few
>custom-type controls (an ICON button, a floating-point slider), and have
>run into a problem.  
>
>First, the setup:  Since I want debugging messages from the CDEF, I'm not 
>compiling it as a CODE resource and linking it into my tester application.
>(I have two windows in my tester app: a drawing window, and a TextEdit window.
>I have an 'AddMessage()' function which takes a character string and appends
>it to the TextEdit window -- instant scrolling debugging message window!)
>
>I'm doing the following to associate my control with the control handle:
>First, I make the window, set the port, and call my setup function:
>
>	gDrawWindow = NewWindow(....blah blah....);
>	SetPort(gDrawWindow);
>	SetupCDEF(gDrawWindow);
>
>Here's the setup function:
>
>SetupCDEF(win)
>	WindowPtr win;
>{
>	Rect r;
>	Handle h;
>
>	h = NewHandle(h);
>	*h = (Ptr)MyControl;   /* MyControl() is my CDEF 'main' function. */
>	SetRect(&r, 160, 30, 400, 60);
>	myCH = NewControl(win, &r, "\p", TRUE, 0, 50, 100, scrollBarProc, 0L);
>
>	HLock(myCH);
>	(*myCH)->contrlDefProc = myH;
>	HUnlock(myCH);
>}
>
>This works, to a point.  'MyControl' intercepts all 'drawCntl' and 'testCntl'
>messages passed through the control handle 'myCH', I'm glad to report, but
>where it messes up is that I don't get any 'initCntl' message(s) at all.
>
>I could, of course, compile this CDEF into a stand-alone CODE resource and 
>use ResEdit to paste it into the test application, making the requisite changes
>to the 'NewControl' statement, (I know how to do that).
>
>I don't want to, however.  I want to see debugging messages.  Is there SOME
>way to link a CDEF function to a control handle so that it can be compiled in
>together with the testing application? 
>
>Thanks in advance for any help anyone out there can provide.  When these CDEFs
>are finished, I will post them to the 'net:  I'm sure they would be useful.
>
>Stephen N. Spencer     |"Now you must dance the dance that you imply!
>ACCAD   (614) 292-3416 | Your actions will follow you full circle round,
>The Ohio State Univ.   | The higher the leap, I said, 
>1224 Kinnear Road      | The harder the ground!"
>Columbus, OH 43212-1163|		- Amy Ray, "Center Stage"
>spencer@cgrg.ohio-state.edu||71160.3141@compuserve.com||stephen_spencer@osu.edu



Hope you don't mind a Pascal example!  This example is straight out of one of
the example programs in the THINK Pascal package.  It's a little different
from what you were doing - you still have to put a little stub CDEF resource
in the resource file, but this procedure modifies it so that it will jump to
the code which is actually in your program. I've used this for doing the 
exact thing that you want to do, so I know it works.  One thing - if you are
having problems with the CDEF doing what it's supposed to do, make sure that
you turn the debugging options off for the actual CDEF code

type
  JmpRecord = record		
    jmpInstr: Integer;
    jmpAddr: Ptr;
   end;
  JmpPtr = ^JmpRecord;
  JmpHandle = ^JmpPtr;

Here is the documentation in the sample program (written by Rich Siegel of
Symantec Corp.):

InstallDefProc works by looking for a resource of the desired resource type 
and ID in the resource file specified by "dpPath"; if you're installing 
definition routines at program startup, you can pass CurResFile() as the
first argument. The defproc resource is then patched to point to the procedure
address given in "dpAddr".

If the resource is not found in the resource file, a debugger trap is executed.
To avoid this, you should create a 6-byte resource (it can be all zeros) of 
the defproc's type and ID, and place it in your program's resource file.

Caveats:

You probably don't want to install stub defprocs in the system resource file. 
Also remember that there are reserved resource ID's from 0 to 127; you should 
use resource ID's 128 or higher for your defprocs.

Procedures that are installed should be in the main segment, and 
InstallDefProc only need be called once.

HOW IT WORKS:

In the normal case, the system loads a procedure resource (a CDEF, for 
example), and jumps to its beginning. InstallDefProc provides this 
functionality for procedures in your program by providing a 6-byte stub 
resource, which contains $4EF9, followed by a long word. The $4EF9 is a 68000 
long jump instruction, and the long word is the address to which to jump. So 
the system calls this dummy defproc,which in turn jumps to the address passed.

procedure InstallDefProc (dpPath: Integer; dpType: ResType; dpID: Integer;
       dpAddr: Ptr);
  var
   jH: JmpHandle;

   savePath: Integer;

 begin
  savePath := CurResFile;
  UseResFile(dpPath);

  jH := JmpHandle(GetResource(dpType, dpID));

  UseResFile(savePath);

  if jH = nil then	{Is there no defproc resource?}
   DebugStr('Stub Defproc Not Found!');

  with jH^^ do
   begin
    jmpAddr := dpAddr;
    jmpInstr := $4EF9;
   end;

  HUnlock(Handle(jH));
  MoveHHi(Handle(jH));
  HNoPurge(Handle(jH)); {make this resource nonpurgeable}
 end;

So you would call it like:
   InstallDefProc(CurResFile,'CDEF',200,@MyCDEFMain);

Then, you would simply create a new control in the normal fashion (using 
NewControl), making sure that its procID field is set equal to 16 * resource
ID of the CDEF stub in the resource file.  So if the CDEF stub had resID 200,
you'd pass 3200 in the procID parameter to NewControl.

I hope this helps some.
- Jim Wintermyre