• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Tax-free use of the Notification Manager at INIT time

Crutch

Well-known member
So I want an INIT to use the Notification Manager to alert the user (once the Finder loads) if it couldn’t run. That’s easy. To do it, though, I have to give _NMInstall an NMRec that includes a pointer to the string I want to display in the alert, which I have to stick in a nonrelocatable block in the System Heap so it doesn’t get wiped before the Finder loads.

So: does anyone know of a way to do this without permanently wasting at least a tiny bit of memory?

It seems I have two choices:
  1. Call _NMInstall with a nmResp value of -1. Then the Notification Manager automatically removes my NM queue element after displaying the alert — however, that nonrelocatable block holding the string I displayed will never be recovered.
  2. Write a response procedure and install it in a nonrelocatable block in the System Heap. My response procedure can call _DisposPtr on my string… however, it can’t dispose of itself while it is running. So, now the memory used for the response procedure code will never be recovered.
Either way it seems I am permanently wasting (nonrelocatable) memory until the next restart. Yes I realize it’s only a few bytes but this seems wrong. Am I missing anything?

@cheesestraws you might know
 

Phipli

Well-known member
So I want an INIT to use the Notification Manager to alert the user (once the Finder loads) if it couldn’t run. That’s easy. To do it, though, I have to give _NMInstall an NMRec that includes a pointer to the string I want to display in the alert, which I have to stick in a nonrelocatable block in the System Heap so it doesn’t get wiped before the Finder loads.

So: does anyone know of a way to do this without permanently wasting at least a tiny bit of memory?

It seems I have two choices:
  1. Call _NMInstall with a nmResp value of -1. Then the Notification Manager automatically removes my NM queue element after displaying the alert — however, that nonrelocatable block holding the string I displayed will never be recovered.
  2. Write a response procedure and install it in a nonrelocatable block in the System Heap. My response procedure can call _DisposPtr on my string… however, it can’t dispose of itself while it is running. So, now the memory used for the response procedure code will never be recovered.
Either way it seems I am permanently wasting (nonrelocatable) memory until the next restart. Yes I realize it’s only a few bytes but this seems wrong. Am I missing anything?

@cheesestraws you might know
I'm not a programmer... But could you write to a file and then check that file with that weird extension type that is really an application that launches in the background when the finder loads? I forget the name of the type of extension sorry (edit : changing the file type to "appe" on an "APPL" I think it was).

At that point you can check for the file and even delete it, before quitting.

The above is a terrible bodge and I'm so sorry for suggesting it, but this is how I learn what is right and wrong in the world of programming.
 
Last edited:

cheesestraws

Well-known member
Am I missing anything?

Here's a slightly wild suggestion. I'm on a train so I don't know if it would work, but might be a starting point.

I think the key here is that, at least according to THINK Reference, HUnlock and HPurge do not move memory.
  1. Allocate a relocatable block in the system heap with the string and your response proc in it. Lock it at INIT time so it doesn't move under you.
  2. When you create the notification manager record, store the handle to the block in nmRefCon, which is marked in IM as "for application use".
  3. When your response procedure is called, as the very last thing it does, unlock the handle (in nmRefCon) with HUnlock and mark it purgeable (or it might work the other way around), then immediately return. HUnlock and HPurge won't move or purge memory until a call that moves memory is made.
  4. When the memory manager needs to, it will purge your block.
 

Crutch

Well-known member
Yes! That should do it. I knew I was missing something simple. (DisposPtr may move memory and I was stuck on the string needing to be locked down since the NMRec needs a pointer to it.)

@Phipli your idea is indeed hacky but I think would also technically work!
 

cheesestraws

Well-known member
At that point you can check for the file and even delete it, before quitting.

This would work, and would be a relatively sensible way of doing it if you were trying to hand off lots of work after the Finder loaded. The thing that I think would make this less good from a user point of view is that I don't think there's a way of having an appe and an INIT in the same file. So the user would have to install two extensions, which feels like a bit of a mess. And yeah, it's definitely overkill for just popping up a notification (though you know that).
 

Crutch

Well-known member
(You could have the INIT generate the app by writing a CODE resource it contains out to disk, then have the app delete itself. But yes it is less elegant and would create a slightly weird UX as the user would see an unexpected app pop open after the Finder loads. I do like the cleverness of it though)
 

Crutch

Well-known member
Oh that’s right. do you know what system version introduced support for appe’s?
 

Crutch

Well-known member
Thanks @cheesestraws , good idea. Here’s my working code.

C:
void Notify(Str255 s)
//    Notify() allocates a buffer to hold (1) a simple response cleanup proc (defined
//    with inline machine instructions below), (2) the NMRec that points to the
//    cleanup proc, and (3) the notification alert string.  It then sticks a locked
//    handle to that buffer in the nmRefCon field of the NMRec (which is in the buffer!)
//    and calls NMInstall with a pointer to the buffer.
//
//      The cleanup proc just calls _NMRemove then HUnlock/HPurges the buffer so the
//    memory manager can kill it later as needed.
//
//    Among other things, this approach ensures we work cleanly:
//
//        - When a cdev posts a notifcation and the main segment has moved by the time
//        it posts
//
//      - When an INIT that fails to load wants to post a notification (so the INIT
//        code including a possible response proc was never detached/locked in the
//        system heap)
//
//    Hardcoding the machine instructions below prevents the hassle of having to
//    compile this into a separate resource and include it in all my projects.
{   
    NMRec *nmRec;
    
    // NOTE this is an array initializer -- need A4/A5 set up first:

    const short cleanUpNMProc[] = // pascal void _CleanUpNMProc(NMRec *nmRec)
        {
            0x206F, 0x0004,       // movea.l   #4(SP), A0    ; get NMRec pointer
            0xA05F,               // _NMRemove                ; dequeue it

            0x2068, 0x0020,       // movea.l   NMRec.nmRefCon(A0), A0
            0xA02A,               // _HUnlock                ; unlock handle from refCon
            0xA049,               // _HPurge                ; free handle to NMCleaner

            0x205F,               // movea.l   (SP)+, A0    ; stash return address
            0x588F,               // addq.l    #4, SP        ; pop arg from stack
            0x4ED0                // jmp       (A0)            ; return
        };

    typedef struct {
        char            nmResponder[sizeof cleanUpNMProc];
        NMRec           nmRec;
        unsigned char s[];
    } NMCleaner;

    NMCleaner **nmCleaner;
    OSErr theErr;

    // copy the cleanup proc into an NMCleaner struct in the system heap,
    // include room for notification string, and get a handle to it:

    const THz saveZone = TheZone;
    TheZone = SysZone;

    theErr = PtrToHand(cleanUpNMProc,
                       (Handle *) &nmCleaner,
                       sizeof(NMCleaner) + s[0] + 1);

    TheZone = saveZone;

    if (theErr)                         
    {
        SysError(1001);  // out of memory
        return;
    }   

    // lock our handle since we need to give _NMInstall a pointer into it:

    HLock((Handle) nmCleaner);

    // populate the NMCleaner struct with a notification string and the NMRec fields:

    BlockMove(s, (**nmCleaner).s, s[0] + 1);

    nmRec = &(**nmCleaner).nmRec;

    nmRec->nmResp    = (ProcPtr) &(**nmCleaner).nmResponder;  // point to our machine code
    nmRec->nmStr    = &(**nmCleaner).s[0];
    nmRec->nmRefCon = (long) nmCleaner;  // so our response proc can get/free handle to itself
    nmRec->qType    = nmType;
    nmRec->nmMark   = 0;
    nmRec->nmIcon   = NULL;
    nmRec->nmSound  = NULL;

    if (NMInstall(nmRec) != noErr)
        SysError(1002);  // should never fail
}
 
Top