Batch Processing
Volume Number: | | 9
|
Issue Number: | | 5
|
Column Tag: | | Jörg's Folder
|
Related Info: Apple Event Mgr Edition Manager Event Manager
Batch Processing with Apple Events
Solutions for a specific batch task
By Jörg Langowski, MacTech Magazine Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
This column was initiated by a readers question:
Dear Jörg,
I am looking for a way to simulate batch files on the mac. I need to run two Fortran programs sequentially, then open Excel and run a macro on the results. What should I use? Can it be done? If you can solve this I'll subscribe!
Homer Bartlett, Microstar Inc.
Well, we always welcome new subscribers, and your question is actually of very general interest; so this month Ill write about batch processing with Apple Events. This is actually a rather ambitious title for what well be doing, sending a couple of Apple Events to other programs to make them do their things; but didnt the headline catch your eye? So there.
I have found three principal ways to install a way of batch processing like Homer Bartlett asked for. a) you write a Fortran program that sends an oapp Apple Event to each of the two programs that you want to execute. These two programs leave intermediate results in files. Then, the Fortran program opens Excel (again via an Apple Event), and makes Excel execute a macro file. How this is done, youll see soon. b) you write a Fortran program that calls the two other programs via Apple Events as before, but the intermediate results are left in edition files that can be handled by System 7s Edition Manager. Excel 3.0 and higher supports Subscribe and Publish; if an Excel worksheet is opened that subscribes to those edition files, the data will automatically be updated when the edition is modified. And, last one and easiest one, c) you control everything from Excel - the execution of the two Fortran programs and the following running of the macro. Ill give you examples for all three of these procedures.
First of all, lets write two little Fortran programs that calculate our results and write them to files. Theyre at the start of the example: one calculates a sine curve, the other an exponential, and the Excel macro will later open the files and read them into a worksheet where the results are multiplied and displayed in a graph.
Opening an application from Fortran
Language Systems Fortran provides a rather convenient interface to the four basic required Apple Events. There is a routine F_SendEvent that takes 3 parameters: first a character*4 constant which is the Apple event ID, then a string that specifies the target application and finally an optional document name (also a string). Thus, to open Microsoft Excel from a Fortran program, you might include the line
call F_SendEvent(oapp,Microsoft Excel,)
and to open the worksheet SinExp from Excel
call F_SendEvent(odoc,Microsoft Excel,SinExp)
These commands are contained in the main program of listing 1. First, in fact, we call the two programs that generate our data, both using the oapp event, and then we send the odoc event to Excel for opening the macro sheet that opens and multiplies the two output files. Since the program continues after sending the oapp event without waiting that the called program has actually written the file, we check whether the output files are actually there before continuing with the macro execution. We assume here that Excel is already open; I did not want to hard code a line that contained the full access path for opening Excel, because that is obviously different from Macintosh to Macintosh. So make sure Excel is open before you run the program.
When the Excel macro sheet has been opened, Excel is ready for executing the macro. Now, how do you make Excel execute a macro - or any command for that matter - from the outside, by sending an Apple event? This is documented in two places: You can either get the Excel 4.0 Software Development Kit from Microsoft, or download a file that you can find on AppleLink where the Apple events are described that Excel understands. I downloaded the file, you find it in the archives of the Apple Events discussion in Developer support:System 7 talk. Its somewhere in April/May 1992; search for Excel and youll find it; it is a posting by Ben Waldman from Microsoft. The file with the full description of the Apple events understood by Excel is too long to be printed here (45 pages of MS Word text, that would take a full issue of this magazine!). But here, we need only one very basic Apple event, the dosc event that is becoming a kind of a standard for remote command execution; for instance HyperCard and the MPW tool server also understand it. The dosc event contains a text parameter, which is simply a script of commands to execute by the program; in HyperCard, those are Hypertalk commands and in Excel, any valid Excel command or function.
Language Systems Fortran contains a function that lets you send a script to another program through the dosc event. The script lines are defined in a character array, e.g.,
character *40 script(10)
will allow you to write a script of 10 lines of 40 characters maximum each. The function call
Error = F_SendScript('Microsoft Excel',script,10)
would then send the ten lines to the application Microsoft Excel, which executes them. In our case, we send only one line
'RUN("SinExp!R1C1") '
which is the command to execute the macro starting at row 1, column 1 in the open macro sheet SinExp. Note that Excel scripts that are sent by Apple events never contain equal signs in front of the command, unlike the same command contained in a worksheet or macro sheet.
So here you go - we have created a Fortran program that calls two other programs and then runs an Excel macro on the results. But there are two other ways to achieve a similar result.
Publish/Subscribe
First of all, one can use the Edition manager, part of System 7. Using this service, a program can publish an edition of some data, and another program can subscribe to it. The publisher and subscriber are then automatically linked together through Apple events so that the data that the subscriber sees gets automatically updated when the publisher changes it.
Language Systems Fortran supports the Edition manager. When you open a Fortran file, write data to it, and close it with status=publish, you create an edition of that file. Excel 4.0, for instance, can subscribe to such an edition. Doing that from Excel is easy: you select a range of cells and then select Subscribe to from the Edit menu. You can then select an edition file which will be used to update automatically this selected range of cells. In the source code disk, I have already defined a worksheet that contains subscribers to the two data files that are generated by the two Fortran programs.
Listing 2 gives the program example. Here the two data-generating parts are implemented as subroutines that can be selected from a menu in the main Fortran program. When you first write the data files, the editions are created. Excel can then subscribe to them, which is done in the worksheet, and a graph is linked to the data. When you then go back to the Fortran program and recalculate the data in the two files with other parameters, youll see that the graph also changes (almost) instantaneously.
Calling Fortran programs from Excel macros
This is the last possibility that I found, and it might be the easiest one. There is a function in Excel called EXEC( ) which allows you to execute a program from within Excel. Listing 3 shows a macro which calls the two Fortran programs that create the data files, and then does the calculations and creates a graph like the previous macro. You dont need to compile a main Fortran program as in the first example, but just execute a sequence of Excel commands which may even be contained in a text file. In fact, this last example might come closest to answer Homer Bartletts original question, which was a way to simulate batch files on the Macintosh.
Last remark: I have seen a lot of discussion going on lately about Frontier, a scripting system for the Macintosh developed by UserLand Software. Although I have never seen the product, Id like to write about it sooner or later when I get hold of it and find the time for writing.
See you next month.
Listing1: Calling other Fortran programs and an Excel macro from Fortran
a. The called programs
(compile and link using the RunNoWindow script)
program doExp
real exptab(100),period
parameter (period=30.0)
do j = 1,100
exptab(j) = exp(-j/period)
end do
open(10,file='expData')
write(10,100) exptab
close(10)
100format(x,f10.2)
end
program doSine
real sintab(100),period
parameter (twopi = 6.283185,period = 15.0)
do j = 1,100
sintab(j) = sin(twopi*j/period)
end do
open(10,file='SinData')
write(10,100) sintab
close(10)
100format(x,f10.2)
end
b. The main program
cSends commands to excel via the 'dosc' event.
c
cCompile with -bkg=4.
c
program SendScript
integer*2 err, F_SendScript
external F_SendScript
character*30 ch
logical exists
ch = 'RUN("SinExp!R1C1") '
type *, 'Generating Data '
Error = F_SendEvent('oapp','sinus','')
call checkError(Error)
Error = F_SendEvent('oapp','expo','')
call checkError(Error)
exists = .false.
do while (.not. exists)
inquire(file='expData',exist=exists)
end do
exists = .false.
do while (.not. exists)
inquire(file='SinData',exist=exists)
end do
type *, 'Opening Macro sheet '
Error = F_SendEvent('odoc','Microsoft Excel', *
'SinExp')
call checkError(Error)
type *, 'Running Macro '
Error = F_SendScript('Microsoft Excel',ch,1)
call checkError(Error)
type *, 'Quitting Excel '
Error = F_SendEvent('quit','Microsoft Excel','')
call checkError(Error)
end
subroutine checkError(Error)
integer*2Error
character*10 ch5
if (Error <> 0) then
write(ch5,'(i10)') Error
call alertbox('Error '//ch5//' occurred.')
write(*,*) 'Error '//ch5//' occurred.'
stop
end if
end
c. The Excel macro
"=OPEN(""expData"")"
"=OPEN(""SinData"")"
"=SELECT(""R1C2"")"
"=FORMULA(""=expData!RC[-1]*RC[-1]"")"
"=SELECT(""R1C2:R100C2"")"
=FILL.DOWN()
"=NEW(2,1)"
"=GALLERY.LINE(2,TRUE)"
"=ACTIVATE(""SinData"")"
=CLOSE(FALSE)
"=FILE.DELETE(""SinData"")"
"=ACTIVATE(""expData"")"
=CLOSE(FALSE)
"=FILE.DELETE(""expData"")"
=RETURN()
Listing 2: Excel/Fortran interface via Publish/Subscribe
program PublishDemo
integer*2Error,F_SendEvent
external F_SendEvent
external doPublish,doExit
call moveoutwindow(20,40,300,300)
call addmenuitem('Publish','Publish Sine',doSine)
call addmenuitem('Publish','Publish Exp',doExp)
write(*,*)
write(*,*) 'Opening Application Excel '
write(*,*)
write(*,*) 'Opening SinExpTable '
Error = F_SendEvent('odoc','Microsoft Excel', * 'SinExpTable')
call checkError(Error)
write(*,*)
write(*,*) 'Opening SinExpChart '
Error = F_SendEvent('odoc','Microsoft Excel', * 'SinExpChart')
call checkError(Error)
write(*,*)
write(*,*) 'Use the Publish menu to update the Excel
1 chart.'
call SetExitProc(doExit)
end
subroutine checkError(Error)
integer*2Error
character*5ch5
if (Error <> 0) then
write(ch5,'(i5)') Error
call alertbox('Error '//ch5//' occurred.')
write(*,*) 'Error '//ch5//' occurred.'
stop
end if
end
!!mp inlines.f
subroutine doSine
real sintab(100),period
parameter (twopi = 6.283185)
type *,"Enter period of sine in steps: "
accept *,period
do j = 1,100
sintab(j) = sin(twopi*j/period)
end do
open(10,file='SinData')
write(10,100) sintab
close(10,status='Publish')
100format(x,f10.2)
end
subroutine doExp
real exptab(100),period
type *,"Enter decay period in steps: "
accept *,period
do j = 1,100
exptab(j) = exp(-j/period)
end do
open(10,file='expData')
write(10,100) exptab
close(10,status='Publish')
100format(x,f10.2)
end
subroutine doExit()
integer*2Error,F_SendEvent
external F_SendEvent
Error = F_SendEvent('quit','Microsoft Excel','')
call checkError(Error)
end
Listing 3: Calling Fortran programs from an Excel macro
(this assumes that the Fortran programs are in the same folder as Excel)
"=EXEC(""expo"")"
"=EXEC(""sinus"")"
"=WAIT(NOW()+""00:00:05"")"
"=OPEN(""expData"")"
"=OPEN(""SinData"")"
"=SELECT(""R1C2"")"
"=FORMULA(""=expData!RC[-1]*RC[-1]"")"
"=SELECT(""R1C2:R100C2"")"
=FILL.DOWN()
"=NEW(2,1)"
"=GALLERY.LINE(2,TRUE)"
"=ACTIVATE(""SinData"")"
=CLOSE(FALSE)
"=FILE.DELETE(""SinData"")"
"=ACTIVATE(""expData"")"
=CLOSE(FALSE)
"=FILE.DELETE(""expData"")"
=RETURN()