FB II Compiler







Disk I/O














Made with FB


Install a gestalt selector

Here's a nightmare scenario of my experiences in trying to install a custom Gestalt selector. I thought I'd share the solution with you and I'm hoping that someone knows a better way to do this. (Warning: you need to know some assembler to follow this.)

I was trying to install a custom Gestalt selector so that I could keep a variable alive in between different executions of a WDEF resource that I'm writing. The way that you are supposed to install a custom Gestalt selector is this:
1. Inside an INIT code resource, set up a Gestalt response procedure in a certain way.
2. In your INIT install routine, call NewGestalt using a special selector of your own (ex. _"MyGS") and the address of your response procedure.
3. From a tester application or code resource, call the Gestalt routine with the selector value _"MyGS". Gestalt will call the response procedure that you previously set up, which will return the value that you want. I used the FB INIT shell to help set this whole thing up.

FB does not have the NewGestalt call implemented and does not return the osErr value of the main Gestalt call, so I looked up the headers in my assembly language files and double-checked them in Inside Mac. Usually it's pretty easy to write assembler glue, but this is what it took to get it working:

- first I tried converting the Pascal headers into stack-based calls, but the return value from my Gestalt response procedure was always 0

- then I tried using register-based calls, as documented in Inside Mac, but the return value was still always 0

- finally, sticking to register-based calls, I called MacsBug from my Gestalt response procedure to examine the stack. I'd had my test program print the correct return address and I'd written this down, so I could find this on the stack. I calculated an offset to this address by hand and programmed my Gestalt routine to send a test value to that address. It finally worked! (Trouble was, this return address was way past where it should have been in the stack, given the small number of parameters that had been pushed onto it.)

- then I tried completing the Gestalt response routine by having it return not just a test value, but the actual value of the variable I wanted. It kept sending gibberish--turned out, after considerable poking around, that FB wouldn't let me change the value of a global in a code resource (at least it didn't in this one). So I had to revert back to the assembler method of creating space inside the code itself (using a "dc.w 0,0" directive), copying my variable to that space, and using that location to access the value of my variable from my gestalt response procedure.

Finally it worked!

Like I say, there must be an easier way to do this. I'm a little worried that the stack offset to the accurate return address will change on different machines under different OS's, which would mean that my method would only work properly on my machine (of course, I could just put a flag on the stack and search for that, but I'd rather do things properly).

Does anyone know how to implement custom Gestalt calls properly? I'm always reluctant to do such low-level hacking.


Oh boy, this sounds familiar.

Rather than walking through your explanation and attempting to explain how to make it all work, I'm just going to include my Gestalt code. This is a very weird approach in some ways, but it _works_. (Mad ppatter! has used this for close to five years, and an estimated 17,000 users have reported no conflicts.)

The first function, TrueGestalt, is just a version of Gestalt that matches the official parameter list. I've also included NewGestalt and ReplaceGestalt, which FB does not implement.

The important function is FN MakeGestaltHandler. Pass it a selector code and a size (in bytes). It will create and install a Gestalt handler from the assembly code that follows. This gestalt handler will stay resident until the system restarts, even if your program quits and is reloaded in between.

This selector's return value is the address of a record. The record's size is the number of bytes you specified when you called MakeGestaltHandler.

This code was designed to let a cdev, an INIT, and an invisible daemon app talk to each other. All three share a standard preferences record. The INIT installs the Gestalt handler, giving it the size of the preferences record. From then on, anytime part of the program wants to look at the preferences, it just calls Gestalt and gets the value from the record returned.


LOCAL FN TrueGestalt(selector&,@gestaltResponse&)
  ` subq.l #2,sp
  ` move.l ^selector&,-(sp)
  ` move.l ^gestaltResponse&,-(sp)
  MACHLG &225F
  MACHLG &201F
  MACHLG &2288
  MACHLG &3E80
  ` move.w (sp)+,D0
  ` ext.l D0

LOCAL FN NewGestalt(selector&,gestaltFunction&)
  ` subq.l #2,sp
  ` move.l ^selector&,-(sp)
  ` move.l ^gestaltFunction&,-(sp)
  MACHLG &205F
  MACHLG &201F
  MACHLG &3E80
  ` move.w (sp)+,D0
  ` ext.l D0

LOCAL FN ReplaceGestalt(selector&,gestaltFunction&,oldGestaltFunction&)
  ` subq.l #2,sp
  ` move.l ^selector&,-(sp)
  ` move.l ^gestaltFunction&,-(sp)
  ` move.l ^oldGestaltFunction&,-(sp)
  MACHLG &225F
  MACHLG &205F
  MACHLG &201F
  MACHLG &2F09
  MACHLG &225F
  MACHLG &2288
  MACHLG &3E80
  ` move.w (sp)+,D0
  ` ext.l D0

  DIM handlerPtr&,codeSize&
  DIM codeAddr&
  DIM osErr
LOCAL FN MakeGestaltHandler(selector&,size&)
'Copies some code out of a spot below and makes a new Gestalt global variable with it.
  codeAddr&=LINE "GenericGestalt"
  codeSize&=LINE "EndGenericGestalt"-codeAddr&
'there, we know where the code is, and how big it is. Copy it into anew pointer block:
  handlerPtr&=FN NEWPTR _sys_clear(codeSize&+size&-4)
  LONG IF handlerPtr&
'did we get the pointer block? Good, copy our code there:
    BLOCKMOVE codeAddr&,handlerPtr&,codeSize&
'Now make this a new Gestalt selector:
    osErr=FN NewGestalt(selector&,handlerPtr&)

` move.l A7,A0 ;copy stack pointer to afree register
` add.l #$4,A0 ;bump it forward so we have the address of the result pointer
` move.l (A0),A0 ;look up the value
` lea storageblock(pc),A1 ;get the address of the storage block
` move.l A1,(A0) ;poke it into the value pointed at by the result thing on the stack
` move.l (sp)+,A0 ;get the return address
` add.l #$8,sp ;knock the excess parameters off the stack
` move.w #$0,(sp) ;put "no error" into the error code
` jmp (A0) ;go back to where we came from
` dc.l #$00000000

One other thing to point out re/Gestalt selectors:

From the sounds of it, all you want to do is save some persistent variables for your WDEF; you're not sharing data with any other program. In your case, there's probably no need for an INIT. Just check everytime your WDEF is initialized - if your Gestalt selector is not installed, then install it. Otherwise, just use the existing selector code.

There are no ill effects from installing a Gestalt selector after system startup. You just have to make sure that the selector code is inside the System heap (otherwise NewGestalt will fail), which the MakeGestaltHandler function does.