ShadowM's GEOS Programming Tips(last updated 2010-08-08) |
This is a collection of GEOS programming tips 'n' tricks that I've collected over the years. I continue to learn this fascinating subject, and new tips are being added as I come across things that I think might be helpful to others.
To create the icon for an application (24x21 pixels), you can paste it directly into the source code for the application's header module; this will work if it you cut it at 3x3 cards in geoPaint (24x24 pixels). geoPaint will not copy any finer than on card boundaries.
On the other hand, if you are creating a new file programmatically and building a header template in code (to pass to SaveFile in r9), that will not work and you have to convert the icon to .byte directives and enter it by hand.
If your program is made up of several source files and you are including symbol files and macros, take care not to pass symbols to the linker more than once; it fills up the symbol table needlessly. Another good idea is to create your own symbol file with only the ones you need rather than including geosSym. The includes for the first source file should look something like this:
.if Pass1
.include shadowSym
.include shadowMac
.endif
Subsequent source files (including VLIR modules) should have their includes declared like this:
.if Pass1
.noeqin
.noglbl
.include shadowSym
.include shadowMac
.glbl
.eqin
.endifDon't let your source files get too big. I recently had an issue where all eight source files making up an app assembled correctly, but the linkage editor failed on an "Expression cannot be resolved" error. The error message then printed the offending line of source... which contained only the single character 'p'. My first thought was that I had bumped the keyboard and introduced a stray character in the source, but then it wouldn't have assembled. I went over the source file with a fine-toothed comb and didn't find anything, so I finally decided to split the file into two, and voila! the linker is happy.
Even though geoProgrammer 1.1 fixed a lot of bugs, it will still act squonky once in a while, especially with large projects. I recently got a "symbol defined more than once" from the linker that was definitely not an error; I finally just looked up the symbol and replaced it with the absolute address on that line, and the error message went away. Sometimes you have to get a little creative.
One thing you can count on regardless of which geoProgrammer version you use is that the page and line numbers in an error listing will sometimes be wrong. This is partly because the assembler seems to count expanded macros as multiple lines of source, but you'll also see incorrect page numbers. For example, I just assembled a source file that had a branch out of range near the end of page 6; the error message pointed to page 7, line 84. I made a mistake when I fixed it, and on the next assembly, the error message said page 6, line 84. Go figure.
You may also get a "Branch out of range" error when there doesn't appear to be one; this usually happens in an area with a lot of local labels where global labels are far apart. Experienced GEOS programmers know that adding another global label amongst the locals will usually result in a clean assembly.
InitForIO is not re-entrant. It saves state, which is restored by DoneWithIO. What this means is that if you call InitForIO a second time without having called DoneWithIO in between, the saved state is overwritten and you can't return to it. When you do call DoneWithIO, the kernal will not be banked back in and the machine will crash as soon as any GEOS APIs are called.
GetString keeps running during MainLoop, until the user hits Return. If you need to terminate string input manually (e.g. as part of a menu or icon handler), just use this code, which essentially fakes a keypress:
LoadB keyData,#$0d lda keyVector ldx keyVector+1 jsr CallRoutine
Since GetString keeps running during MainLoop, you may have a problem if you call PutString at the same time, because the two APIs share some common system variables. I encountered this when a process handler called PutString while GetString was running; this is the code I use to save and restore those variables:
PushB $87cf ;stringLen PushB $87d0 ;stringMaxLen PushB alphaFlag ;save cursor state PushW leftMargin PushW rightMargin PushW StringFaultVec ... jsr PutString ... PopW StringFaultVec PopW rightMargin PopW leftMargin PopB alphaFlag PopB $87d0 PopB $87cf
If you're using DBGETSTRING in a dialog box, don't use an OK icon. The string won't get null-terminated until the user hits Return, so if he types a large number of characters and then backspaces over some of them, they will all be returned as part of the buffer (this is why GEOS filename dialogs don't have an OK icon). Check for DBGETSTRING in r0L when DoDlgBox returns instead.
Don't use LdFile. The BSW programmer's guide says that the variables loadOpt and and loadAddr can be used to control whether the file's load address is overridden, but doesn't say where these variables live (geosSym doesn't either). Boyce says $886B and $886C, but that didn't work for me, so I looked in the Hitchhiker's Guide, which had this to say:
"All versions of LdFile to date under Commodore GEOS are unusable because the load variables... (loadOpt and loadAddr) are local to the Kernal and inaccessible to applications. Fortunately this is not a problem because applications can always go through GetFile to achieve the same effect." Caveat utilitor.
Here is the entire macro (or rather, macros), and here are some excerpts with comments:
if @(arg0)=80,print"type: SUB_MENU"[cr]This line expects an address and reads the byte at that address, using the
@ operator. The value is tested against $80 (hex is
the default radix).
if @(arg0)=c0||(@(arg0)&3f)!=0,print @(arg0):"unknown type: "[cr]Another lookup. If only bits 6 and 7 are set, or if any of the low bits are set, print the value preceded by the heading "unknown type: ".
print @@(arg0):8b'"menu item text: "[cr]This one prints eight bytes in character format (
') with the
heading "menu item text: ", starting at the address
(@@) located at the argument.
.macro showitem setu 1,arg0+(5*u.fn)[cr] menuitem u.1[cr] .endm .macro menuitms print @(arg0+6)&3f:."no. items: "[cr] setu 0,(@(arg0+6)&3f)-1[cr] for .0:u.0,showitem (arg0+7)[cr] .endmThese two macros show several features. In
menuitms, we
set user register zero (u.0) with the setu command. We
are setting it to the number of items in the menu (lower six bits at
offset six in the menu structure) minus one, to use as a counter. We
then call showitem in a for loop. This macro was broken
out separately after getting the infamous nesting error. We pass the
first byte of the menu item list (offset seven); the called macro sets
user register one (u.1) to the beginning of the current menu item
structure using the loop counter u.fn (menu item
structures are five bytes long), and calls the menu item display
macro.
Here's some sample output (note that the dispatch addresses are all the same for desk accessories):
>menu geosMenu top/bottom: .15 .99 left/right: .0 .100 type: VERTICAL|CONSTRAINED no. items: .6 menu item text: info.set type: MENU_ACTION dispatch: $0437 menu item text: text man type: MENU_ACTION dispatch: $0561 menu item text: photo ma type: MENU_ACTION dispatch: $0561 menu item text: ScreenPh type: MENU_ACTION dispatch: $0561 menu item text: ruler1.5 type: MENU_ACTION dispatch: $0561 menu item text: printIt1 type: MENU_ACTION dispatch: $0561 >I guess I could refine the macro that prints the menu item text...