home *** CD-ROM | disk | FTP | other *** search
/ ftp.mactech.com 2010 / ftp.mactech.com.tar / ftp.mactech.com / util / cscriptthread.sit.hqx / CScriptThread / CScriptThread.cp next >
Text File  |  1997-08-13  |  15KB  |  520 lines

  1. /*---------------------------------------------------------------------------
  2.  
  3.     Copyright © 1996 CPTech Ltd. All rights reserved. 
  4.     
  5. -----------------------------------------------------------------------------
  6.  
  7.     PROGRAM DOCUMENTATION
  8.  
  9.     Program Name    : CScriptThread.cp
  10.     Module           : Script execution
  11.      Description        : This module runs an OSA script in a thread.
  12.                        In theory with out erroring on AESend because
  13.                        the events are suspended in communications.
  14.  
  15.     
  16.     There seems to be a few problems with executing AppleScripts in Threads at 
  17.     the moment. The idea of acalling OSALOad and OSAExecute works fine in 
  18.     68K code and I'm running multiple concurrent scripts. The 68K application
  19.     works fine on a PowerPC but recompile for PPC and things go awry as soon
  20.     as I try to run the second threaded script. 
  21.     
  22.     A fix has been supplied by Mac DTS in the form of Preserve68KRegsActiveProc.
  23.     
  24.     I also have the following mail from the same:
  25.  
  26.  
  27.         On 10/31/96, Caerwyn Pearce, 100024.355@compuserve.com wrote:
  28.  
  29.         >I seem to be having a few problems with executing AppleScripts in Threads. 
  30.         >The idea of calling OSALoad and OSAExecute works fine in 68K code and 
  31.         >I'm running multiple concurrent scripts. 
  32.         >
  33.         >The 68K version of the application works fine on a PowerPC but recompile
  34.         >for PPC and things go awry as soon as I try to run a second threaded script. 
  35.         >The threads are co-operative for both 68K and PowerPC.
  36.  
  37.         The problem isn't with A5, but with the Thread Manager. There is a bug in 
  38.         the Thread Manager with regards to native PPC threads and the OSA. When 
  39.         you call your native active proc, the PPC registers are saved and 
  40.         restored, but the emulated 68K registers aren't. Problem is, the OSA 
  41.         assumes they are.
  42.  
  43.         So, to work around the problem I put together a PPC library. It basically 
  44.         a piece of 68K code wrapped up in a PEF container that you link into your 
  45.         project. You install this library as the active proc, which then saves 
  46.         and restores the emulated registers around a call to the real PPC active 
  47.         proc (which is doing the yielding).
  48.  
  49.         BTW, the active proc is the proper place to be making any yield calls. 
  50.         The idle proc is for getting periodic time; you should never call any 
  51.         toolbox routine that gives up control (WaitNextEvent, Yield, etc).
  52.     
  53.     And then another
  54.         >You mention that the active proc is the best place to Yield, not the idle 
  55.         >proc. Do you exclude the send proc as well.
  56.  
  57.         Well, yes and no. If you want to yield time in the send proc, before or 
  58.         after you have send the event, that's OK. But once you call AESend, and 
  59.         if you specify kAEWaitReply (which is the reason you have an idle proc), 
  60.         you should never call anything that yields time to other processes (any 
  61.         Yield call, WaitNextEvent, EventAvail, etc.) 
  62.  
  63.         The reason for this is that when you call AESend, the Apple Event Manager 
  64.         takes over responsibility for calling WaitNextEvent for you. That's why 
  65.         the parameters to the idle proc are an event record and a sleep value. 
  66.         The Apple Event manager gives you the event record, and you give it back 
  67.         the sleep time. If you do cause a process switch by yielding things can 
  68.         and do get messed up, and a crash is often the result.
  69.  
  70.         BTW, the only events you will receive in the idle proce are events like 
  71.         update and suspend. You will never receive any high level events (like 
  72.         Apple Events). That's what the filter proc (in the call to AESend) is 
  73.         for. And you MUST service any update events you receive, because no other 
  74.         process will get any time until they are services.
  75.  
  76.         The Active Proc, on the other hand, is there to provide a way for the 
  77.         process that is executing the script to yield time to other processes. 
  78.         That's why the active proc is the right (and only) place to yield time to 
  79.         other processes 
  80.  
  81.  
  82.         >Does your library include all three procs, should I wish to
  83.         >implement my own idle proc too.
  84.  
  85.         The library is simply a wrapper that saves the emulated registers, calls 
  86.         a UPP for the real active proc (where you yield), and then restores the 
  87.         registers when the active proc returns. It's general purpose enough that 
  88.         I suppose it could be used for other procs, but since you shouldn't yield 
  89.         in any other procs, I never tested it with them.
  90.  
  91. -----------------------------------------------------------------------------
  92.     REVISION HISTORY
  93. Rev        Date        Who    Description
  94. -----------------------------------------------------------------------------
  95. VCS01    23-OCT-96    CRP    Incept Date
  96.  
  97. -----------------------------------------------------------------------------
  98. */
  99.  
  100.  
  101.  
  102.  
  103. //---------------------------------------------------------------------------
  104. #pragma mark Includes
  105.  
  106. // System headers
  107. #include <OSA.h>
  108. #include <AppleEvents.h>
  109. #include <MixedMode.h>
  110.  
  111. // PowerPlant headers
  112. #include <UAppleEventsMgr.h>
  113. #include <UExtractFromAEDesc.h>
  114.  
  115.  
  116. // Project headers
  117. #include "CScriptThread.h"
  118.  
  119.  
  120.  
  121. //---------------------------------------------------------------------------
  122. #pragma mark Prototypes
  123.  
  124. pascal OSErr CustomOSASendProc(const AppleEvent        *theAppleEvent,
  125.                     AppleEvent            *reply,
  126.                     AESendMode             sendMode,
  127.                     AESendPriority        sendPriority,
  128.                     long                timeOutInTicks,
  129.                     AEIdleUPP            idleProc,
  130.                     AEFilterUPP            filterProc,
  131.                     long                refCon);
  132.  
  133. pascal OSErr CustomOSAActiveProc (Int32 refCon);
  134.  
  135.  
  136.  
  137. //---------------------------------------------------------------------------
  138. #pragma mark Globals
  139.  
  140. // === Static Members ===
  141.  
  142. OSAActiveUPP    CScriptThread::sAEActiveProcUPP            = NewOSAActiveProc(CustomOSAActiveProc);
  143. OSASendUPP        CScriptThread::sAESendProcUPP            = NewOSASendProc(CustomOSASendProc);
  144.  
  145.     
  146.  
  147.  
  148. #pragma mark -
  149. #pragma mark ----- Constructors/Destructors -----
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156. // ---------------------------------------------------------------------------
  157. //        • CScriptThread
  158. // ---------------------------------------------------------------------------
  159. //    Constructor
  160.  
  161. CScriptThread::CScriptThread(FSSpec &inSpec)
  162.     : LThread(false),
  163.     mFileSpec(inSpec)
  164. {
  165.     mComponent            = nil;
  166.     mScriptID            = nil;
  167.     mResultID            = nil;
  168.  
  169.     mSendProc            = nil;
  170.     mSendProcRefCon        = nil;
  171.  
  172.     mActiveProc            = nil;
  173.     mActiveProcRefCon    = nil;
  174.  
  175.     mContextAppleEvent.descriptorType = typeNull;
  176. }
  177.  
  178.  
  179. // ---------------------------------------------------------------------------
  180. //        • ~CScriptThread
  181. // ---------------------------------------------------------------------------
  182.  
  183. CScriptThread::~CScriptThread(void)
  184. {
  185.     OSErr            osError            = noErr;
  186.     OSErr            osaError        = noErr;
  187.  
  188.     osaError = ::OSADispose(mComponent,mScriptID);
  189.     osaError = ::OSADispose(mComponent,mResultID);
  190.     osError = ::CloseComponent(mComponent);
  191. }
  192.  
  193.  
  194. // ---------------------------------------------------------------------------
  195. //        • Run
  196. // ---------------------------------------------------------------------------
  197. //    This is the code executed by the script thread.
  198. //        Open a scripting component and set it up for concurrency.
  199. //        Open the file
  200. //        Get the script handle
  201. //        Load it into the component
  202. //        Close the file
  203. //        Execute it.
  204.  
  205. void    *CScriptThread::Run(void)
  206. {
  207.     Handle            scriptResH        = nil;
  208.     short             fRefNum            = -1;
  209.     OSErr            error            = noErr;
  210.  
  211.     Try_
  212.     {
  213.         /*
  214.         ** Need to have an instance of a component for each thread
  215.         */
  216.  
  217.         mComponent = ::OpenDefaultComponent(kOSAComponentType,
  218.                                             kOSAGenericScriptingComponentSubtype);
  219.         ThrowIfNil_(mComponent);
  220.         
  221.         // The apple event manager calls the idle function
  222.         // only after an event has been sent.
  223.         // A scripting component calls the active proc at regular intervals.
  224.         
  225.  
  226.         // open resource fork
  227.         fRefNum = ::FSpOpenResFile(&mFileSpec,fsRdPerm);
  228.         ThrowIfResError_();
  229.  
  230.         // get the first script resource in the file
  231.         scriptResH = ::Get1IndResource(kOSAScriptResourceType,1); 
  232.         FailNIL_(scriptResH);
  233.  
  234.         // Load it
  235.         AEDesc     scriptDesc;
  236.         scriptDesc.descriptorType = typeOSAGenericStorage;
  237.         scriptDesc.dataHandle = scriptResH;
  238.         error = ::OSALoad(mComponent,&scriptDesc,kOSAModeNull,&mScriptID);
  239.         ThrowIfOSErr_(error);
  240.  
  241.         ::CloseResFile(fRefNum);
  242.         fRefNum = -1;
  243.  
  244.         // Use my own Active and send procedures
  245.         error = SetupOSACallBacks();
  246.  
  247.         // run the script(finally)    
  248.         error = ::OSAExecute(mComponent,mScriptID,kOSANullScript,kOSAModeNull,&mResultID);
  249.         
  250.         if(errOSAScriptError == error)
  251.         {
  252.             StAEDescriptor    theErrorDesc;
  253.             Int16            theError;
  254.             
  255.             error = ::OSAScriptError(mComponent, kOSAErrorNumber, 
  256.                                     typeShortInteger, &theErrorDesc.mDesc);
  257.         
  258.             ThrowIfOSErr_(error);
  259.  
  260.             UExtractFromAEDesc::TheInt16(theErrorDesc, theError);
  261.             
  262.         
  263.         }
  264.     }
  265.     Catch_(catchErr)
  266.     {
  267.     }
  268.     EndCatch_
  269.  
  270.     if (fRefNum != -1)
  271.         ::CloseResFile(fRefNum);
  272.  
  273.  
  274.     return (NULL);
  275. }
  276.  
  277. // ---------------------------------------------------------------------------
  278. //        • SwapContext
  279. // ---------------------------------------------------------------------------
  280. //    This function defines the output thread's behaviour
  281.  
  282. void    CScriptThread::SwapContext(Boolean swappingIn)
  283. {
  284.     OSErr    error;
  285.     
  286.     
  287.     if (swappingIn)
  288.     {
  289.         if(mContextAppleEvent.descriptorType != typeNull)
  290.         {
  291.             error = ::AESetTheCurrentEvent(&mContextAppleEvent);
  292.         }
  293.         
  294.         LThread::SwapContext(swappingIn);
  295.     }
  296.     else
  297.     {
  298.         error = ::AEGetTheCurrentEvent (&mContextAppleEvent);
  299.         
  300.         LThread::SwapContext(swappingIn);
  301.     }
  302. }
  303.  
  304.  
  305.  
  306. // ---------------------------------------------------------------------------------
  307. //        • SetupOSACallBacks
  308. // ---------------------------------------------------------------------------------
  309. //
  310. // Get and Set send and active functions for the component
  311. // Get because we're going to call the default one anyhow.
  312. // I have to implement my own send cos otherwise the AESend 
  313. // fails when I suspend events, causing the script to bomb out.
  314. // The alternative would be to put 'ignoring errors' in all the 
  315. // scripts, messy.
  316.  
  317. OSErr CScriptThread::SetupOSACallBacks(void)
  318. {
  319.     OSErr    finalErr    = noErr;
  320.     OSErr    error;
  321.     
  322.  
  323.     error = ::OSAGetSendProc(mComponent, &mSendProc, &mSendProcRefCon);
  324.  
  325. #if GENERATINGCFM
  326.     mSendRefRec.procEntry    = sAESendProcUPP;
  327.     mSendRefRec.refCon    = (long)this;
  328.     
  329.     error = ::OSASetSendProc( mComponent, &Preserve68KRegistersSendProc, (long)&mSendRefRec);
  330. #else
  331.     error = ::OSASetSendProc(mComponent, sAESendProcUPP, (long)this);    
  332. #endif
  333.     
  334.     // Get and Set the Active proc for the component
  335.     // In theory the active proc is called periodically during 
  336.     // script compilation and execution.    
  337.     error = ::OSAGetActiveProc(mComponent, &mActiveProc,& mActiveProcRefCon);
  338.  
  339.     /*
  340.     ** For 68k we just install a straight call into our Active proc.
  341.     ** If we are PPC then we have to install the DTS fudge to call
  342.     ** back a PPC routine from the 68k emulated OSA.
  343.     */
  344.  
  345. #if GENERATINGCFM
  346.     mActiveRefRec.procEntry    = sAEActiveProcUPP;
  347.     mActiveRefRec.refCon    = (long)this;
  348.     
  349.     error = ::OSASetActiveProc( mComponent, &Preserve68KRegsActiveProc, (long)&mActiveRefRec);
  350. #else
  351.     error = ::OSASetActiveProc(mComponent, sAEActiveProcUPP, (long)this);    
  352. #endif
  353.     return(finalErr);
  354. }
  355.  
  356.  
  357. // ---------------------------------------------------------------------------------
  358. //        • CallOldActiveProc
  359. // ---------------------------------------------------------------------------------
  360.  
  361. OSErr CScriptThread::CallOldActiveProc(void) const
  362. {
  363.     OSErr            theError    = noErr;
  364.  
  365.     if(mActiveProc)
  366.     {
  367. #if GENERATINGCFM
  368.         theError = CallUniversalProc(mActiveProc, uppOSAActiveProcInfo, mActiveProcRefCon);
  369. #else
  370.         theError = mActiveProc(mActiveProcRefCon);
  371. #endif
  372.     }
  373.     return(theError);
  374. }
  375.  
  376.  
  377. // ---------------------------------------------------------------------------------
  378. //        • CallOldSendProc
  379. // ---------------------------------------------------------------------------------
  380.  
  381. OSErr CScriptThread::CallOldSendProc(const AppleEvent        *theAppleEvent,
  382.                             AppleEvent            *reply,
  383.                             AESendMode             sendMode,
  384.                             AESendPriority        sendPriority,
  385.                             long                timeOutInTicks,
  386.                             AEIdleUPP            idleProc,
  387.                             AEFilterUPP            filterProc)
  388. {
  389.     OSErr            theError    = noErr;
  390.  
  391.     if(mSendProc)
  392.     {
  393. #if GENERATINGCFM
  394.         theError = CallUniversalProc(mSendProc, uppOSASendProcInfo,
  395.                         theAppleEvent, reply, sendMode, sendPriority,
  396.                         timeOutInTicks, idleProc, filterProc, mSendProcRefCon);
  397. #else
  398.         theError = mSendProc(theAppleEvent, reply, sendMode, sendPriority,
  399.                         timeOutInTicks, idleProc, filterProc, mSendProcRefCon);
  400. #endif
  401.     }
  402.     else
  403.     {
  404.         theError = ::AESend(theAppleEvent, reply, sendMode, sendPriority,
  405.                     timeOutInTicks, idleProc, filterProc);
  406.  
  407.     }
  408.     return(theError);
  409. }
  410.  
  411.  
  412.  
  413. // ---------------------------------------------------------------------------------
  414. //        • TestAEReply
  415. // ---------------------------------------------------------------------------------
  416.  
  417. OSErr CScriptThread::TestAEReply(AppleEvent *reply)
  418. {
  419.     OSErr            theError    = noErr;
  420.     StAEDescriptor    temp;
  421.  
  422.     theError = ::AEGetParamDesc(reply, keyAEResult, typeWildCard, &temp.mDesc);
  423.  
  424.     return(theError);
  425. }
  426.  
  427.  
  428.  
  429. // ---------------------------------------------------------------------------------
  430. //        • CustomOSAActiveProc
  431. // ---------------------------------------------------------------------------------
  432.  
  433. pascal OSErr CustomOSAActiveProc (Int32 refCon)
  434. {
  435.     CScriptThread    *myThread     = (CScriptThread*)refCon;
  436.     OSErr            theError    = noErr;
  437.  
  438.     if(myThread)
  439.     {
  440.         theError = myThread->CallOldActiveProc();
  441.         myThread->Yield();
  442.     }
  443.  
  444.     return(theError);
  445. }
  446.  
  447.  
  448.  
  449. // ---------------------------------------------------------------------------------
  450. //        • CustomOSASendProc
  451. // ---------------------------------------------------------------------------------
  452.  
  453. pascal OSErr CustomOSASendProc(const AppleEvent        *theAppleEvent,
  454.                             AppleEvent            *reply,
  455.                             AESendMode             sendMode,
  456.                             AESendPriority        sendPriority,
  457.                             long                timeOutInTicks,
  458.                             AEIdleUPP            idleProc,
  459.                             AEFilterUPP            filterProc,
  460.                             long                refCon)
  461. {
  462.     CScriptThread    *myThread     = (CScriptThread*)refCon;
  463.     OSErr            theError    = memFullErr;
  464.     Boolean            bUseAESend    = true;
  465.  
  466.  
  467.         
  468.     /*
  469.     ** As far as hanging about a bit goes the sendMode can contain one 
  470.     ** of the following three options..
  471.     **
  472.     **        kAENoReply            sender doesn't want a reply to event
  473.     **        kAEQueueReply        sender wants a reply but won't wait
  474.     **        kAEWaitReply        sender wants a reply and will wait
  475.     **
  476.     ** The only one which we can wait on is kAEWaitReply.
  477.     ** What happens if the user says kAENoReply and I suspend the event?
  478.     **
  479.     */
  480.  
  481.     if (myThread)
  482.     {
  483.         /*
  484.         ** May want to check the target, if it's me then set the 
  485.         ** timeOutInTicks to kNoTimeout, cos some of my events have
  486.         ** integrated timeout parameters.
  487.         **
  488.         ** Possibly may want to supply an alternate idleProc
  489.         ** The idle proc is used to handle other incoming events!
  490.         ** and can also be used to yield whilst the event is being handled.
  491.         */
  492.         theError = myThread->CallOldSendProc(theAppleEvent, reply, sendMode, sendPriority,
  493.                             timeOutInTicks, idleProc, filterProc);
  494.  
  495.         AESendMode smReplayFlags = sendMode & kAEWaitReply;
  496.         
  497.         if((theError == errAETimeout) && (smReplayFlags != kAENoReply))
  498.         {
  499.             Int16 tempAEErr;
  500.             
  501.             do
  502.             {
  503.                 tempAEErr = myThread->TestAEReply(reply);
  504.                 myThread->Yield();
  505.             } while(tempAEErr == errAEReplyNotArrived);
  506.             
  507.             theError = noErr;
  508.         }
  509.  
  510.     }
  511.     else
  512.     {
  513.         theError = ::AESend(theAppleEvent, reply, sendMode, sendPriority,
  514.                         timeOutInTicks, idleProc, filterProc);
  515.     }
  516.  
  517.  
  518.     return(theError);
  519. }
  520.