[1"z A contribution to a programming section, by J.Bickers (JJB TEMPLAR) {This is the zeroth edition of the programming section, edition one will be on Newsdisk #22. -ed} This discourse is split into 3 sections, as follows: 1) Problems. 1.1) Starting C programs from the WB. 1.2) The 'chip' keyword. 1.3) Dimensioned extern far arrays. 2) Comments. 2.1) Defending your favorite language. 2.2) A plea to C programmers. 2.3) Cleanup operations. 3) Useful (?) ideas. 3.1) Traversing lists. Everything applies to the Lattice C compiler package, version 5. 1 PROBLEMS: 1.1 Starting C programs from the WB. Hey, don't laugh. It took me several hours to get to the bottom of this. The initial problem was this:- I had a suitable icon for a freshly written program. I click on the icon to start the program. It loads fine. A little stdio window opens. I hadn't expected that, but since I had my own routines set up anyway I didn't care. It runs fine, opening its own output console. It even began to end okay, closing its own output window. The stdio window, however, stuck around. And a 'software error - task held' requester appeared. This all happened several months ago. I took a short look into the source code for c.a, spotted what I thought were a few bugs, then fixed them and relinked. This didn't fix the main problem. So I tried to look up the definitions for stdio, etc. Found them, decided they weren't worth the effort to decipher, and instead turned back to c.a. I was under the impression that the window being opened was the sm_ToolWindow of the program's icon. So I commented out the bit to open this console. A good deal of astonishment was created when the window opened despite this. Interpreting this as a bad omen, I quit trying to get stuff to run from the WB. Then very recently I decided to make a determined effort to beat this thing. So, hours were spent isolating the cause of the problems to the line jsr _main(pc), finding that sm_ToolWindow is usually NULL, and that stdin(a4) is not necessarily zero because of that. I found this out by inserting intentional crash statements in various places and noting if the crash point was hit or not, a hideous business. My commodore and amiga keys are beginning to stick down. I set up a _main function that contained just a rts, so I knew that whatever the compiler did behind my back, it wasn't _main opening the window. Finally I realised that there is a THIRD function involved here. That is, the -u option to asm meant that the offending function wasn't "_main", but "__main" (this is when you realise how much extra code the compiler adds). THEN I recalled a _main and _tinymain mentioned in the manual, and found umain.c in the source code directory of Lattice Disk #4. The __main function sets up the stdio files, and is located in umain.c. Things followed fairly quickly from there, resulting in a slightly modified c.a (named startup.a) and umain.c. These modifications are: c.a - added a clr.l stdin(a4) to initialize stdin to zero. umain.c - added a line to close the stdio window if !argc. I placed startup.o and umain.o into the LIB: directory, and linked a program test.c as 'FROM LIB:startup.o+LIB:umain.o+test.o' rather than using 'FROM LIB:c.o+test.o'. There are still some problems, in that my test.a (the rts instruction) left a task waiting around after it finished. However, the C version works fine. So I intend to use these files in the above manner. A braver person would no doubt replace the c.o and umain.o modules altogether (using oml for umain.o in lc.lib). However, I also discovered that if you don't want stdio files, you can use the normal LIB:c.o if you add the following line to your .lnk file: "DEFINE __main=__tinymain", as the tinymain doesn't mess around with stdio. This is the preferable approach for Clock, for example. Note that you can alter the stdio window's parameters if you edit the umain.c file. Also note that stdio is required if you start from the CLI for some programs, and not when starting from WB (for example, my asktask program needs stdio to respond to a '?' parameter from the CLI, and nothing from the WB. The ideal solution here would be a modified umain that set up CLI stdio, but not WB stdio). So in summary: - If you want stdio:- o link with the modified startup.o and umain.o instead of c.o - If you don't want stdio:- o just add the line "DEFINE __main=__tinymain" to your normal .lnk file. UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM The above makes interesting reading, I'm sure, but I just spent some time with oml and omd tracing the general execution path for programs, and came up with:- c.o starts everything off -> __main (umain.c) -> _main (yours or mine) assume finish through rts -> __main (umain.c) -> _exit (exit.c) the exit() function, flushes ANSI file buffers -> __exit (uexit.c) calls __dclose for all open UNIX file blocks which don't have their NC (NoClose) flag set. __main makes (for WB) stdin closable, but not stdout or stderr, thus avoiding trying to close the same file three times. -> XCEXIT (c.o) this fudges the stack, and rts's to terminate. I compared the umain.o produced by compiling umain.c, and the umain.c got via oml from lc.lib, and discovered: THEY ARE NOT THE SAME! So umain.c doesn't need to be modified (and the modification didn't affect programs that called exit() anyway), but the new umain.o should still be considered seperate from the one in lc.lib. What the difference is I don't know, but I presume a flag is mis-set in the lc.lib one. (Maybe my copy is corrupted) Also note that clearing stdin to zero isn't really necessary either, as it's part of the BSS, which c.o automatically inits to zero. So no startup.o is required. The proper solution is to replace the umain.c in lc.lib, and otherwise go back to normal. The replacement is done by: 1) compile umain.c to give umain.o 2) rename umain.o to umain.c 3) from the directory containing umain.c (really an object file), enter'oml LIB:lc.lib' 4) then type 'r umain.c' return 5) then CTRL-\, or whatever end-of-file indicator your shell uses. The thing trundles along a bit, then your prompt reappears. The files umain.x and umain.r will automate this, but oml and lc need to be in the command path (x umain.x). Now, for the more adventurous, I have included two more files: tmain.c - a replacement for umain noWBcons.c - a supplementary file to tmain To use these, delete umain from lc.lib, and add these two. Then if you want to have a program start from the WB without a con:, but you do want stdio from the CLI (for a ? parameter, at least!), you just add the line int noWBcons = 1; /* MUST be initialized DATA */ to your code somewhere, and hey presto! The file noWBcons just contains a definition of the noWBcons variable, in case its not defined in your program, so the linker can find it. It then becomes part of the BSS, and is initialized to zero. The int... line above defines it as DATA, which is initialized to one. See asktask.c for an example of its use. If you wish to keep the status quo, just leave out the line. Nothing could be simpler! If you are VERY space concious, note that using tmain and noWBcons add a few bytes to your final code, even if you don't use noWBcons. noWBcons adds 4 bytes, and tmain adds 36, plus whatever the linker adds in hunk data. Note: theengs teke e wheel en me HD, se eef ye heve lc.lib en e fleeppy ye mey went te geet eet eente RAM: befere eseng oml. - demn vewel kees (e, e, e, end e) er steck! UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM UM And notes: - I still believe there are several bugs in c.a. For instance, it appears to me that when the sm_ToolWindow is closed, the DOS library has already been closed. Much worse is that it seems as if ExecBase is used as the library pointer! This doesn't trigger though, because usually sm_ToolWindow isn't opened in the first place. - The function Forbid is called before replying to the WB message, but I couldn't find an Enable anywhere. But the machine still multitasks when the program is finished. So who does the enable? Perhaps when the task terminates the scheduler removes the task's Forbid? {I see the same thing in Modulla-II programs - a Forbid before sending WB a termination message, and then no Permit anywhere - I assumed it was done in the exit code linked in by Modulla-II. -ed} - I fished out my original WB disk, and booted off it. My test program, using the standard umain and c.o, worked fine. What I use now is a blend of WB1.3, WB1.2 and ARP. Ty works fine from my current setup, so either I'm peculiarly plagued by problems, or our editor has already fixed it for his compile setup. {Ty (my version of Less1.0) was my first real attempt at C hacking, I first had to convert the code from Aztec to Lattice (as that's all I had), and I worked from other examples to do the interface with Workbench; accept multiple (extended selection) files from Workbench etc. I didn't have to modify any of the Lattice C modules, but that was a much earlier version of Lattice C (I did all that 3 years ago). -ed} 1.2 The 'chip' keyword. I have linking problems with this keyword and 'unmerged data items'. I do not want to link whole programs to load into chipmem, so I currently create chip data in standalone modules, to compile with the -ad option on the compiler, and forego the use of 'chip'. Does anyone have a working example of its use? I would like to use it in the standalone modules so I can add code or fastmem (well, slowmem) storage definitions to the same files. 1.3 Dimensioned extern far arrays. First, let me outline the system I use for procuring image data. I used to load BitMaps at run-time, and cut from them the required data (jjb & je both use this method). A far more elegant way, however, is: o Draw the images into a pic. o Use mi to generate a data module. o Set up a header file containing extern far definitions of the image data arrays, and use this header file where access to the data is required. Now I dimension the arrays (just my preference), so instead of: extern far UWORD *imagedata; I would have, say: extern far UWORD imagedata[900]; It is often necessary to index these arrays with a non-zero value. This occurs in two places: 1) the data section, example: struct BitMap bm = {30,20,0,2,0,&imagedata[0],&imagedata[300],}; 2) the code section, example: if (condition) bm.Planes[1] = &imagedata[300]; /* Usual data */ else bm.Planes[1] = &imagedata[600]; /* A shadow mask */ Because of a bug somewhere in the compiler, I think, the code references don't work properly. In the code example above, for instance, bm.Planes[0] would be set to &imagedata[300] regardless of the condition. Examining the running code with cpr shows that the else statement will execute properly, but the value used for &imagedata[600] is the same as that for &imagedata[300]. Whichever reference comes first has its proper value bound to all other references into the same array. This problem hit me ages ago, and I temporarily abandoned coding in image data. This process is too convenient to ignore, however, so here is a workaround - it occured to me that the data section references were OK. So define a local (to the module, but global to functions within the module) array that contained the correct index references. For example: UBYTE *indices[3] = { (PLANEPTR)(&imagedata[0]), (PLANEPTR)(&imagedata[300]), (PLANEPTR)(&imagedata[600]) }; and use this array in the code sections. The general solution is to use (module) local definitions only, within the code sections. For an example, see the source code for my devo program (NOTE: this program is MINE - it is UNFINISHED - you may NOT modify or use it!!!!!!!! - it is provided for EXAMPLE ONLY) in the pics.h and draw.c files. So in summary: When using extern far arrays, do not index them within code. Use (module) local data definitions instead (an extra level of indirection). And notes: If any other Lattice users have a patch for the compiler to fix this, feel free to send me a copy. I only registered recently, so I haven't gotten anything. 2 COMMENTS: 2.1 Defending your favorite language. o Origins: Bell Labs - Thomson, Ritchie, Kernighan, the list goes on and on... Modula-2 on the other hand comes from Wirth, the same fellow who brought us Pascal (turn pale, spin and faint at the horrid memories!).  {To be fair, Pascal was meant to be a teaching language, Modulla-II is meant to be an implementation of the same philosophy for real world uses. -ed} o Basic assumption: programmers know essentially what they are trying to do, and don't need or want protection from the machine. I would refuse to admit that a language that couldn't crash the machine easily would be nice. I equate that with tedious and less powerful. {Yes they know what they're trying to do, and they ignore the uninteresting detail - they expect the compiler to took after the mundane things and clean-up after them (like catch those one hundred and one programming slips made at 2am, like using uninitialised pointers). Unfortunately, C doesn't help. -ed} o Writability (?): very easy to learn and write. Quite easy to read (usually). Have you seen the Modula-2 module to open windows and screens? {That particular example is simply due to the fact that Modulla-II doesn't provide for initialising data storage. And of course if you're trying to write re-enterant code, you'd have to follow suit in C too. If you really want to, there are ways to stick a block of data into a TDI Modulla-II program, to save yourself from writing a bit of code to do the initialisation. -ed} o Compactness: source a good deal more compact than assembler. I sympathise with assembly only programmers, but in the world of multi- megabyte machines most operations are better coded in C. The speed often only needs to keep up with humans, and memory usage is an increasingly insignificant factor (thinking of programmer welfare here). The generated code is good, and getting better as optimizers get better, though remember the compiler writer's motto: "[Optimization pass] Making a wrong program worse is no sin" - Bill McKeeman, Wang Institute. o Software tools: make (developed for C, but goes beyond language restrictions), source code debugger, etc. o Fellow users: me (ha!). No actually, our editor, Toebes, UNIX people, the Commodore people, etc. o Power: as powerful as assembler, though not as fast as GOOD assembler, or as small. Few hidden details (everything can be gotten at one way or another - its just a matter of time, witness the WB startup problem). o On being a super-macro-assembler: all compiled languages, from the heights (?) of Ada to the depths of Pascal, just convert statements in that language to statements in assembler. In that sense, all compilers for any language are just super-macro-assemblers. This is what makes programs like yacc possible (yet another compiler compiler). The super-macros here are the language definition expressions, in Backus-Naur Form or whatever yacc uses. {Aaah, but they don't just translate code, most high level languages throw in a lot of extra code, such as stack checking, array bounds checking, initialised variable checking, subroutine parameter type checking, keyboard interupt (^C) checking - lots of bits that you haven't explicitly coded. In Assembler and C there is in the first case none, and in the second very little, of this overhead. So there is very much a one for one translation excercise going on. In languages like Pascal or Modulla-II there is a great deal of this hand-holding going on, of course in most implementations you can turn off all this extra 'fluff' when you're satisfied the thing's working properly, and end up with a simular sized program to that obtained in C - but a lot less painfully. I've just read an article in Doctor Dobbs Journal (an edition around mid 1987) about someones programming language called PL/68k *or something simular). This language is very much a translator - there is a very direct one for one correspondence between sourcecode and the assembly language produced, you specify registers etc directly, but it has the advantage of being syntactically a subset of C!! You can write and debug you program in this C like code, using a C compiler and whatever high level debugging features you have available, then when you've finished the program, you run the same source code through an assembler (well a translator perhaps) to produce pure 68000 assembly code!!!! Sounds like a very interesting concept. -ed} For those who may not know, any compilable computer language can be defined as a set of expressions defining each element of the language. A valid program consists of these expressions combined to form a graph that can be parsed into executable code. These expressions are restricted in various ways, to prevent infinite loops from occuring in the parsing process, and to make all expressions resolvable. The whole thing is fairly simple, but since I'm writing this from memory, let's not go into detail (my memory is badly fragmented). {Don't ask me why I go on about Modulla-II, when I've never written a program in it (despite owning TDI Modulla II compiler), but I do find C a real pain - progrmming is only fun if the computer does what you want it to - not what you tell it too - C takes you too literally!!! -ed.} 2.2 A plea to C programmers. Please include a '?' parameter to give some program info from the CLI, then exit() (or prompt for params - whatever), so people have a standard way of determining the function of a program. Very few of the programs on the newsdisks do this, though most of the DOS commands do. This applies to other languages too, but I don't know how easy it is to achieve. It is simplicity itself in C. {I would go further and request that the programs ALWAYS prompt for parameters after displaying a template in response to a ? parameter - the advantage is that the program is at that stage loaded into memory and ready to execute - you can then swap disks or whatever and type in the parameters to set it going. This is very useful with a single drive machine, not only are there lots of single drive Amigas out there (all models came out with 1 drive as standard), but so often I end up working on a crippled machine (trying to set up a hard disk, or with the second drive faulty or missing), that I find it very annoying that many of the CLI commands don't work this way (ever tried "execute ?"). -ed} 2.3 Cleanup operations. How many people out there would abort after a failed OpenWindow, say, without bothering to close libraries, screens, fonts, or whatever else is already opened? I was under the impression this was slack coding. 3 USEFUL (?) IDEAS: 3.1 Traversing lists. Assuming you don't use the exec list functions, a list looks something like: list -> node -> node -> ... -> NULL where list = node pointer | NULL, node = [data,list]. (I know the above definition isn't consistent, but if it was, how many of the audience would I lose?) Now to traverse the list and say, insert a node, one generally keeps two pointers, one to the previous node and one to the current node. When one finds a decent spot for the new node, one updates the previous node to point to the new node, and the new node to point to the previous nodes old next node. If you've ever done this, you may have had two special cases: 1) the new node is the first one, 2) the new node is the last one. The 2nd case is very simply dealt with. The 1st, however, requires some peculiar code with regard to the value of the previous pointer (when there is no previous node). A solution is to make the list pointer a dummy node (note the dummy nodes in gel lists), but this may be inappropriate in some cases, and is wasteful anyhow. Another approach is to use casts to make the list pointer look like a node, and perhaps keep the next-node pointers at the beginning of the node structure. This is perhaps an 'inelegant' way to do it, because of the inappropriate cast. The solution I favor is to make the previous node pointer actually a pointer to a pointer to a node (ie: a node handle). This is quite straightforward, but I didn't realise it until recently because I hadn't considered the previous pointer as anything but a node pointer. For an example, look at the polygon.c module, and the list insertion and sorting sections of the drawpoly() function. NOTE: I have written an assembler version of this module (from the output of omd - BTW: who wants some info on converting the omd output to decent assembler?) that is a good deal faster, fixes some bugs, and achieves the main purpose of creating the module - to clip the polygon to rectangular boundaries - so vertices can lie outside the rastport. I don't believe AreaMove and AreaDraw do this. This C module is freely usable (though it may NOT be converted to any other machine except as an official student project - say for the graphics course at Victoria. It is the result of my taking COMP349, lecturer Dr R.Aviv. Note that Dr Aviv is one of those responsible for there being Macs there instead of Amigas. Apparently software for the Macs (especially a compiler) was easier and cheaper to get). The assembler module however is in a state of continuous development, and will eventually be part of a shareware package. If you wish to get ahold of it, a shareware fee ($5 + disk & postage) is involved, payable to me. The assembler version may not be ported, regardless of who you are. (Copyright infringers note: I may not be the Ken Thomson of the Amiga world, but I could easily make a letter bomb!). {I don't know about letter bombs but I certainly do know how to make an Amiga bomb! (thank Christian Johnsen for GOMF!) -ed.}