SHDesigns: Embedded Systems Design, Consulting and Developer Resources Page hits:

Rabbit Programming Tips

Understanding Dynamic C Libraries

Dynamic C (DC) is an odd compiler that does not have normal separate compiliation and libraries. Understanding how DC uses library files (.lib) will help immensly on writing your own libraries. The odd way DC compiles libraries can manifiest itself with unexpected errors outside the library.

Dynamic C was supposedly based on one of the early Small C compilers or one of ots derivatives. This compiler can only compile one file. DC has hacked the code to handle multiple files. To do this, it needs to parse them more than once. Some is compiled at #use, more may be compiled after your program.

First, here is a sample lib:

/* START LIBRARY DESCRIPTION *********************************************
SSCANF.LIB
	Copyright (c) 2002, SHDesigns, www.shdesigns.org
	Updated 6-08-2004 to support whitespace in format string.
DESCRIPTION:
	String conversion.
SUPPORT LIB'S:
END DESCRIPTION **********************************************************/
/* START FUNCTION DESCRIPTION ********************************************
htol                       <SSCANF.LIB>
SYNTAX: long int htol(char *s);
PARAMETER1: Character string to convert
KEYWORDS: convert
DESCRIPTION: Hex String to long int conversion.  "s" is the string to convert,
RETURN VALUE:	long integer.
END DESCRIPTION **********************************************************/
/*** BeginHeader  htol */
long int htol(char * s);
/*** EndHeader */
long int htol(char *s)
{
	long int t;
	char neg;
	neg=0;
	t=0;
 if (*s=='-') {s++; neg=1;}
 while (isxdigit(*s))
  { t<<=4;
    if (isalpha(*s))
	  { t|=(toupper(*s)-'7')&0xf;
	  	 s++; }
	  else
	  t|=(*s++-'0')&0xf;
	}
    if (neg) return(-t); else return(t);
}

 

The two "DESCRIPTION" comment blocks are described in the DC documentation. They are used by the function help in DC. They will not be described here. The comments with "BeginHeader" and "EndHeader" are significant and are actually compiler directives, not comments.

The BeginHeader statement basically says "read this section first up to the next EndHeader. The lables I include on this line remember them and come back here later when you need to find them."

In normal C programming, you have a .c file with the code and a .h file with the definitions and prototypes. DC has merged them in one file. The most important thing to understand is how DC parses the files and your program. The steps are as follows:

  1. Compile bios file (rabbitbios.c)
  2. Start to compile your program
  3. For every #use file.lib, pause compiling the current file, open the .lib file and parse everything inside a BeginHeader and EndHeader block. Ignore everything outside these areas.
  4. When a BeginHeader statement is followed by a variable or function name, save the name of the file, the line number and the variable/function name in a table for later use.
  5. When the end of the lib is reached return to the previous file.
  6. After all the #use commands are processed, it will compile the rest of your program.
  7. At the end of your program, it will have lots of unresolved variables and functions. It uses the table of file/line #/name to find where these are defined.
  8. It then goes back to each .lib as needed and compiles starting at the line # in the table to satisfy the variable for function.

Note that a lib is read more than once. Part is done at the #use statement and the the remaining source may be read later. This works fine when everything is correct; but any errors and odd things can happen.

To best describe this, here's a buggy lib (with line #s):

1: /* in file xyz.lib */
2: /* Beginheader func1 */
3: int func1(int param)   /******* missing semicolon ******/
4: /* Endheader */
5:
6: #define MY_DEFINE 7
7:
8: int func1(int param)
9: {
10:     ......
11:}

And a trivial main.c file:

1: /* main.c */
2: #use xyz.lib
3: main()
4: {
5:    while (1)
6:    {
7:       func1(MY_DEFINE);
8:    }
9:// end of program **** note missing closing parenthesis

There are two syntax errors in the files. The first is a semicolon missing from line 3 of xyz.lib. The second is a missing closing parenthesis at the end of main.c. There is also another error that will occur.

So, when we compile the main.c file. The following occurs:

  1. DC Compiles rabbitbios.c and processes the #use staements within it.
  2. DC starts compiling main.c
  3. At line 2, the #use causes it to start reading xyz.lib
  4. DC ignores everything outside the BeginHeader block. On line 3, it reads a function prototype for func1()
  5. While looking for a semicolon it reaches the EndHeader, stops compiling xyz.lib and returns to main.c line 3
  6. Continuing on in main.c DC errors out looking for the missing semicolon on line 3 "Main.c - Missing semicolon - line 3"

So, a typo in the lib, caused an error in another file. Lets say we fixed the missing semicolon on xyz.lib, line 3. Now we compile again:

  1. As before, compile main.c then when reraching the #use xys.lib, compile everything inside teh BeginHeader/EndHeader blocks. This works fine.
  2. On line 7 of main.c it errors out: "main.c - line 7 - Undefined variable or define MY_DEFINE"

MY_DEFINE is defined in xyz.c, but it is defined outside a BeginHeader/EndHeader block, so it was ignored.

Ok, now we move the #define MY_DEFINE up within the BeginHeader/EndHeader block and compile again.

This time it compiles all the way through your program and reaches the end of the file. It is still looking for the closing parenthesis on the main() function. There are also undefined variables and functions calls. It will take the first undefined variable and search the table generated with the BeginHeader labels to go into that lib file and compile from that point.

Suppose it needed something from STDIO.LIB, then it would get an error compiling STDIO.LIB because of the missing parenthesis in your main.c.

The example shows how errors can "carry-over" between your .c file and the .lib files. Understanding how these errors occur, can help finding them.

Another example:

/* abc.lib */
/* BeginHeader func2 */
int sfunc(char * s);
int func2(int param);
/* EndHeader */
int sfunc(char * s)
{
 ...
}

int func2(int param);
{
 ...
}

/* BeginHeader xxx */
....
/* EndHeader */

Notice the BenginHeader only references func2.

You use this lib in two programs. One works, the other can not find sfunc(). Both programs call sfunc(); so why does one work and the other not?

If a program calls func2(), DC will need to resolve it after compiling your program, so it "sees" the "func2" label in the BeginHeader line. It will then go back to abc.lib at the line after the BeginHeader and start compiling until it hits the next BeginHeader line. So both functions will be compiled and it will work.

If a program only uses sfunc(), DC will not find it as it will never see it as it was not told where to find it by a BeginHeader directive. Again, source outside a BeginHeader/Endheader block is only compiled after your program is compiled and it has a BeginHeader line telling it where to go start compiling in the lib to find it.

Now, even if a program used both sfunc() and func2() it can fail. If sfunc() was used first, it would be the first one to go looking for after compiling your program. There is no corresponding label in a BeginHeader directive, so it cant be found. If you used func1() before sfunc(), the compile for func1() would automatically bring in the code for sfunc() and everything works fine.

Yes, this is odd how it is done. It does have one advantage as only what is needed is compiled into your program. Note, that in the last example; if you used func2() and never used sfunc() it would still be included in your program, but never called.


Additional Information: Back to Tips page - SHDesigns Home Page