[comp.sys.mac.programmer] Is this possible with the Resource Manager?

olson@bootsie.uucp (Eric K. Olson) (02/15/91)

If you do GetResource() for a resource that is currently in memory,
you get a Handle to the data as it appears in memory.  Is it possible,
without disturbing the resource in memory, to get the resource data
as it appears on the disk?  Something like:

	SetResLoad(FALSE);
	resHand = GetResource(type, id);
	SetResLoad(TRUE);
	if (*resHand!=NULL) {		/* The resource was loaded */
		DetatchResource(resHand);
		onDiskHand = GetResource(type, id);
		DetachResource(onDiskHand);
		ReattachResource(resHand, type, id);
		}

Would work, if the call ReattachResource() existed.  I don't think
there is any way to do this.  Any ideas?

-Eric



-- 
Eric K. Olson, Editor, Prepare()      NOTE:     olson@bootsie.uucp doesn't work
Lexington Software Design             Internet: olson@endor.harvard.edu
72A Lowell St., Lexington, MA  02173  Uucp:     harvard!endor!olson
(617) 863-9624                        Bitnet:   OLSON@HARVARD

time@ice.com (02/15/91)

In article <1991Feb15.110116.5508@bootsie.uucp>, olson@bootsie.uucp (Eric K. Olson) writes:
> 	SetResLoad(FALSE);
> 	resHand = GetResource(type, id);
> 	SetResLoad(TRUE);
> 	if (*resHand!=NULL) {		/* The resource was loaded */
> 		DetatchResource(resHand);
> 		onDiskHand = GetResource(type, id);
> 		DetachResource(onDiskHand);
> 		ReattachResource(resHand, type, id);
> 		}
> 
> 
In article <1991Feb15.110116.5508@bootsie.uucp> you write:
> 	SetResLoad(FALSE);
> 	resHand = GetResource(type, id);
> 	SetResLoad(TRUE);
> 	if (*resHand!=NULL) {		/* The resource was loaded */
> 		DetatchResource(resHand);
> 		onDiskHand = GetResource(type, id);
> 		DetachResource(onDiskHand);
> 		ReattachResource(resHand, type, id);
> 		}

How about:
	SetResLoad(FALSE);
	resHand = GetResource(type, id);
	SetResLoad(TRUE);
	if (*resHand!=NULL) {		/* The resource was loaded */
		DetatchResource(resHand);
		onDiskHand = GetResource(type, id);
		DetachResource(onDiskHand);
		/* Now move that resource back into place... */
		onDiskHand2 = GetResource(type, id);
		SizeResource(onDiskHand2, GetHandleSize(resHand));
		BlockMove(*resHand, *onDiskHand2, GetHandleSize(resHand));
		ChangedResource(resHand);
		}

sound right?

tim.

-------------------------------------------------------------
Tim Endres                |  time@ice.com
ICE Engineering           |  uupsi!ice.com!time
8840 Main Street          |  Voice            FAX
Whitmore Lake MI. 48189   |  (313) 449 8288   (313) 449 9208

olson@bootsie.uucp (Eric K. Olson) (02/17/91)

In article <1CE00001.ed5n0r@tbomb.ice.com> time@ice.com writes:
>How about:
>	SetResLoad(FALSE);
>	resHand = GetResource(type, id);
>	SetResLoad(TRUE);
>	if (*resHand!=NULL) {		/* The resource was loaded */
>		DetatchResource(resHand);
>		onDiskHand = GetResource(type, id);
>		DetachResource(onDiskHand);
>		/* Now move that resource back into place... */
>		onDiskHand2 = GetResource(type, id);
>		SizeResource(onDiskHand2, GetHandleSize(resHand));
>		BlockMove(*resHand, *onDiskHand2, GetHandleSize(resHand));
>		ChangedResource(resHand);
>		}
>
>sound right?

At first I thought this was a really good solution, but it doesn't do
quite what my theoretical ReattachHandle() would.  I don't understand
the call to ChangedResource(resHand): wouldn't that just fail (it's no
longer a Resource Handle)? If you meant ChangedResource(onDiskHand2), it
doesn't reattach resHand.  Any other old reference to the resource via
resHand remains detached.  New references via GetResource() return a
different Handle (onDiskHand2).  Calls to LoadResource() with the old
Handle will fail.  Please correct me if I'm wrong. 

My purpose for this is editting (or just copying) the currently running
application's MENU resources.  I don't need the changes to take effect
until the next run (for editting).  I _do_ need the resource as it
appears on disk, even though it is in memory and not purgable (and
modified from the data on the disk by the TCL). 


-Eric
-- 
Eric K. Olson, Editor, Prepare()      NOTE:     olson@bootsie.uucp doesn't work
Lexington Software Design             Internet: olson@endor.harvard.edu
72A Lowell St., Lexington, MA  02173  Uucp:     harvard!endor!olson
(617) 863-9624                        Bitnet:   OLSON@HARVARD

olson@bootsie.uucp (Eric K. Olson) (02/17/91)

Well, I went ahead and brute-forced it.

The following code allows me to load the Resource data off the disk, even if
the resource is currently loaded and in use (such as 'MENU' resources of the
currently running application).

Unfortunately, it uses things that are likely to break:  internal structures
of the Resource Manager, and the TopMapHndl low-memory global.  I don't know
if these things are likely to change soon, or if they are available under AU/X.

Isn't there a better way?  Is this what AddReference() once did?

typedef struct resMapHeaderRec {
	long			dataOffset;
	long			mapOffset;
	long			dataLength;
	long			mapLength;
} resMapHeaderRec;
 
typedef struct resMapRec {
	resMapHeaderRec		header;
	struct resMapRec * *	nextMap;
	short			refNum;
	short			attrib;
	unsigned short		typeOffset;
	unsigned short		nameOffset;
} resMapRec, *resMapPtr, **resMapHandle;
	
typedef struct resMapEntryRec {
	short			resID;
	unsigned short		nameOffset;
	unsigned long		lengthOffset;
	Handle			resHandle;
} resMapEntryRec, *resMapEntryPtr, **resMapEntryHandle;
 
Handle	GetResDataFromDisk(type,id)
	ResType			type;
	short			id;
{
	Handle			onDiskHand;
	unsigned long		resMapOffset;
	resMapEntryPtr		resMapEntry;
	resMapPtr		resMap;
	short			inMemRef;
	Handle			inMemHandle;
	
	SetResLoad(FALSE);
	inMemHandle = GetResource(type, id);
	SetResLoad(TRUE);
	if (*inMemHandle == NULL) {
		inMemRef = HomeResFile(inMemHandle);
		resMapOffset = RsrcMapEntry(inMemHandle);
	
		resMap = (resMapPtr) *TopMapHndl;

		/* Find the appropriate resource map */
		
		while (resMap->refNum != inMemRef) {
			resMap = *(resMap->nextMap);
			if (resMap==NULL) return NULL;
			}
		
		resMapEntry = (resMapEntryPtr) (((Ptr) resMap) + resMapOffset);
		
		if (resMapEntry->resHandle!=inMemHandle) return NULL;
		
		DetachResource(inMemHandle);	/* resMapEntry->resHandle = NULL; */

		onDiskHand = GetResource(type, id);
		
		if (onDiskHand != NULL) {
			DetachResource(onDiskHand);
			HNoPurge(onDiskHand);
			}
		
		/* Reattach the old resource handle */

		resMapEntry->resHandle = inMemHandle;
		}
	else {
		/* The normal GetResource case */

		onDiskHand = GetResource(type, id);

		if (onDiskHand != NULL) {
			DetachResource(onDiskHand);
			HNoPurge(onDiskHand);
			}
		}
	}

-- 
Eric K. Olson, Editor, Prepare()      NOTE:     olson@bootsie.uucp doesn't work
Lexington Software Design             Internet: olson@endor.harvard.edu
72A Lowell St., Lexington, MA  02173  Uucp:     harvard!endor!olson
(617) 863-9624                        Bitnet:   OLSON@HARVARD

lsr@Apple.COM (Larry Rosenstein) (02/19/91)

In article <1CE00001.ed5n0r@tbomb.ice.com> time@ice.com writes:
>

>	if (*resHand!=NULL) {		/* The resource was loaded */
>		DetatchResource(resHand);
>		onDiskHand = GetResource(type, id);
>		DetachResource(onDiskHand);
>		/* Now move that resource back into place... */
>		onDiskHand2 = GetResource(type, id);
>		SizeResource(onDiskHand2, GetHandleSize(resHand));
>		BlockMove(*resHand, *onDiskHand2, GetHandleSize(resHand));
>		ChangedResource(resHand);
>		}

I think you may have mis-typed something here.  It doesn't make much sense
to call ChangedResource with something that's been detached (resHand).

Anyway, a more fundamental problem is that the "current" handle to the
resource is onDiskHand2, not resHand.  It's possible that some piece of code
was using resHand, and that code will be screwed.

You have to do this without calling DetachResource.  One way that comes to
mind is:

if (*resHand != NULL) {
		/* save current contents */
	oldSize = GetHandleSize(resHand);
	save = NewHandle(oldSize);
	BlockMove(*resHand, *save, oldSize);

	EmptyHandle(resHand);
	LoadResource(resHand);	/* re-load disk copy */

	diskCopy = resHand;
	HandToHand(diskCopy);	/* make a copy of it */

		/* restore original version */
	SetHandleSize(resHand, oldSize);
	BlockMove(*save, *resHand, oldSize);
	DisposHandle(save);
}

-- 
		 Larry Rosenstein,  Object Specialist
 Apple Computer, Inc.  20525 Mariani Ave, MS 3-PK  Cupertino, CA 95014
	    AppleLink:Rosenstein1    domain:lsr@Apple.COM
		UUCP:{sun,voder,nsc,decwrl}!apple!lsr

olson@bootsie.uucp (Eric K. Olson) (02/20/91)

In a recent article, Larry Rosenstein (lsr@Apple.COM) writes:
>[Re: Reading data from disk while resource in memory]
>
> You have to do this without calling DetachResource.
> One way that comes to mind is:
> 
>if (*resHand != NULL) {
> 		/* save current contents */
> 	oldSize = GetHandleSize(resHand);
> 	save = NewHandle(oldSize);
> 	BlockMove(*resHand, *save, oldSize);
> 
> 	EmptyHandle(resHand);
> 	LoadResource(resHand);	/* re-load disk copy */
> 
> 	diskCopy = resHand;
> 	HandToHand(diskCopy);	/* make a copy of it */
	           ^^^^^^^^
	HandToHand(&diskCopy);

> 
> 		/* restore original version */
> 	SetHandleSize(resHand, oldSize);
> 	BlockMove(*save, *resHand, oldSize);
> 	DisposHandle(save);
> }
> 

This code is more like what I was looking for.  I definitely prefer this
to the rule-breaking solution I posted earlier.  I've tried it (with
the fix to HandToHand()) and it seems to work fine.

Thanks Again, Larry!

It's worth noting that in THINK C (and probably MPW), the call to
HandToHand() in the included code below should be:

	HandToHand(&diskCopy);

It occurs to me that calling DetachResource() on _any_ resource in the
currently running Application could be dangerous (if that resource has
been GetResource()'ed by some other part of your program).  It might
help to think of my application as a parasitic FKEY resource editor
(it's not-- it's the Prepare() View Editor) that can't really know what
resources are in use by the host application.  (Similarly, the View
Editor is intended to work compiled into any other application, whose
objects may be using resources).

Is it possible to know what resources have been gotten and have since
been purged? Or gotten with SetResLoad(FALSE)? You can't call
DetachResource() on those, either, I presume, although you can
HandToHand() it and return the copy, then EmptyHandle() the resource
handle if it was empty before.  Or I could call HPurge() on the
resource handle (if it was empty before), so it might remain
in memory until the next time it's GetResource()'ed.  Is this kosher?

/* Detach a resource, leaving previous references around */

Handle MyDetachResource(Handle aResHandle, Boolean wasLoaded)
{
	Handle		aDataHandle;
	
	aDataHandle = aResHandle;
	HandToHand(&aResHandle);
	if (!wasLoaded) {
		HPurge(aResHandle);    OR     EmptyHandle(aResHandle);
		}
	return aDataHandle;
	}
	

-Eric








-- 
Eric K. Olson, Editor, Prepare()      NOTE:     olson@bootsie.uucp doesn't work
Lexington Software Design             Internet: olson@endor.harvard.edu
72A Lowell St., Lexington, MA  02173  Uucp:     harvard!endor!olson
(617) 863-9624                        Bitnet:   OLSON@HARVARD

lsr@Apple.com (Larry Rosenstein) (02/22/91)

In article <1991Feb20.101151.22358@bootsie.uucp>, olson@bootsie.uucp (Eric K. Olson) writes:
> 
> It occurs to me that calling DetachResource() on _any_ resource in the
> currently running Application could be dangerous (if that resource has
> been GetResource()'ed by some other part of your program).  It might

You also have to be careful about system resources (for example snds), 
because these are shared under MultiFinder.  

> Is it possible to know what resources have been gotten and have since
> been purged? Or gotten with SetResLoad(FALSE)? 

I think you would have to walk through the resource map, because that's
where the handle would be stored if it has been gotten.

Larry