C programs are typically quite large (especially when compared to native assembler). There are two main reasons for this. Least important factor is the translation from c to assembler. This translation produces code which is less optimal than native assembler and therefore slightly larger. However this does not make a significant speed difference. Most of the time, a program is waiting for the user to tell it what to do anyway. For those pieces of code where speed does matter (typically less than 1% of a program) you could still opt for using assembler anyway.
A much more important factor are the libraries which are included in the program. These libraries can make up a large part of the final program. As such that is not a problem, but the same routines are linked with many programs, thus waisting memory when multitasking. Thus we wrote the Dynamic Link Library Manager. When a program uses the DLL Manager, it can link to routines which are separately loaded. In fact, a dynamic link library is just a kind of thing. However, it is used in such a way that the calls to the thing code are a lot more efficient than when using the extension thing mechanism.
Note that the DLL Manager does not load extensions, it only links to them. The direct loading of extensions is not possible, as we can not assume that there is a harddisk with the extensions. Therefore the extensions have to be loaded in advance, and an error will be reported if a dynamic link can not be resolved. (This will normally be handled by the ProWesS loader).
Using a DLL is quite simple. The most important thing is to include the library file when linking your program (by using the -l option in the linker). Depending on the type of DLL used, you may also have to include a special function call to make sure that the linkage structures are built properly by the linker. This is normally a function which does absolutely nothing, but has to be present for the linker only. An example of such a function is LinkPROforma when using PROforma.
It is normally not necessary to use the c68 libraries when using syslib, but
it is possible to combine them. This can be done by including a call to
LinkDLL. This is a function which resolves all the DLL links in the
program. This routine should be the very first routine which is called in
main.
syslib also includes c68 support routines. If you want your program to use the syslib version, the -lsms linker option should be given before the -lc option (which by default is always included last. If you want to use the c68 version instead of the syslib version, the order should be reversed. However, you could of course do the linking for some DLL's other than syslib (e.g. ProWesS or PROforma), in which case you don't have to include the -lsms option and just use the c68 support routines which are part of libc.
Writing a DLL is not difficult. The main work which is involved is the writing of the routines which have to be linked dynamically. After that, you have to decide what type of DLL library you want your set of routines to be. It can either be a simple library (which is built using conv), which is easiest to use, but can wast some memory when the library includes a lot of routines (like syslib and PROforma). The alternative is to use a library which is built using mkdllib. With this kind of library, not all the routines in it are always linked. Also, because the DLL linkage blocks are slightly more complex, you have to give the linker some help by calling a (library specific) routine somewhere in the code, just to help the linker.
As an example, I present here some extracts from the PROforma source code. This contains two DLL's, one of each type. The code initialises the thing which contains both the DLL's and the also allows access for dynamic configuration. Some line numbers are adde in the listing to aid in explaining.
1 #include "thing_h" 2 #include "job_h" 3 4 #include "PROforma_h" 5 6 extern char InitIsDone; /* to see if linking is safe to do */ 7 extern Job PROforma; /* job id for myself */ 8 9 Error PFConfig(char *line); /* routine for runtime configuration */ 10 11 typedef struct { 12 char identifier[8]; /* e.g. "<>" */ 13 void *usercode; /* base address for usercode */ 14 int extension; /* name of extension in thing */ 15 short namelen; /* length of thingname == 12 */ 16 char thingname[12]; /* "PROforma DLL" */ 17 int version; /* " v01" == PF_DLL_VERSION */ 18 short links[1]; /* list of functions to link (VECT only) */ 19 } DLLEntry; 20 21 #define PF_THING_CORE 0x434f5245 /* "CORE" */ 22 #define PF_THING_VECT 0x56454354 /* "VECT" */ 23 #define PF_THING_VERSION 0x312e3130 /* "1.10" */ 24 #define PF_DLL_VERSION 0x20763031 /* " v01" */ 25 26 #define DLL_TYPE_MASK (short)0xc000 27 #define DLL_ID_MASK (short)0x3fff 28 #define DLL_TYPE_JMPL (short)0x4000 29 #define DLL_TYPE_DATA (short)0x8000 30 #define DLL_JMPL (short)0x4ef9
The lines displayed here define all the data structures which are necessary to do the actual linking, and defines some necessary constants. The actual linking code follows here :
32 static Error DLLink(DLLEntry *link) 33 { 34 Job me; 35 36 /* check version */ 37 if (link->version!=PF_DLL_VERSION) 38 return ERR_NIMP; 39 40 JOBMe(&me); 41 if (me!=PROforma) /* I can always link back to myself */ 42 { 43 /* wait until initialisation has finished */ 44 while (!InitIsDone) 45 JOBSuspend(MYSELF,1,NULL); 46 } 47 else 48 { 49 /* if it is me, then I better make sure I don't use my thing once more */ 50 THINGFree(PF_THING_NAME); 51 } 52 53 /* now link the proper area */ 54 switch (link->extension) 55 { 56 case PF_THING_CORE: 57 { 58 extern char core_start[2], core_end[2]; 59 char *dummy=core_end; 60 MEMCopy(dummy-core_start,core_start,link->usercode); 61 } 62 break; 63 case PF_THING_VECT: 64 { 65 extern void *DLLARRAYpf[2]; 66 extern short COUNTpf; 67 void *dest=(void *)((char *)link->usercode+2); 68 short *what, id; 69 70 for (what=link->links ; *what ; what++) 71 { 72 id=(*what&DLL_ID_MASK)-1; 73 if (id>=COUNTpf) 74 return ERR_NIMP; 75 switch(*what&DLL_TYPE_MASK) 76 { 77 case DLL_TYPE_JMPL: 78 *((short *)dest)++=DLL_JMPL; 79 *((void **)dest)++=DLLARRAYpf[id]; 80 break; 81 case DLL_TYPE_DATA: 82 *((void **)dest)++=*((void **)DLLARRAYpf[id]); 83 break; 84 default: 85 return ERR_NIMP; 86 } 87 } 88 } 89 break; 90 default: 91 return ERR_IPAR; 92 } 93 return ERR_OK; 94 }
On line 37, the version is checked. More recent versions are not implemented by this code. This could also be a switch which could react differently depending on the version.
Between line 40 and 51, the code either waits until PROforma is fully initialised, or releases the link. The latter is done because some of the PROforma drivers use PROforma themselves.
The rest of the code checks which DLL has to be linked and does the linking. The CORE routines use the simple linkage blocks (as built using conv and only require the copying of the linkage block.
The VECT DLL uses the slightly more difficult linkage structure as built using gendllib. These libraries can be linked using code as given from lines 64 to 87.
All that remains to be done is actually define and initialise the PROforma DLL thing.
96 static ThingExtension thg_list[]={ { PF_THING_VECT, DLLink }, 97 { PF_THING_CORE, DLLink }, 98 { PF_THING_CNFG, PFConfig }, 99 { THING_LISTSENTINEL } }; 100 101 static ThingDefinition thg_defn={PF_THING_NAME, PF_THING_VERSION, FALSE, 102 NULL, NULL, NULL, NULL, 0, thg_list}; 103 104 Error PFThingInit() 105 { 106 Error err; 107 Thing thing; 108 109 /* remove old thing, unless still in use */ 110 err=THINGRemove(PF_THING_NAME); 111 if (err && err!=ERR_ITNF) return err; 112 113 /* link in new one */ 114 thg_defn.owner=PROforma; 115 err=THINGCreate(&thg_defn); 116 if (err) return err; 117 118 /* make sure the PROforma job uses the PROforma thing */ 119 return THINGUse(PF_THING_NAME,0,&thing,NULL); 120 }
On line 98, you can see an extra extension which allows access for dynamic configuration of PROforma.
The PFThingInit routine simply links in the PROforma DLL thing. It first makes sure that no other version exists (line 110). If this fails (because an older version does exist but is in use), then an error is returned. Otherwise, the new thing is being built (line 115). Because PROforma has the shape of a job, we have to make sure that the the thing is owned by the PROforma job (line 114), and that the job is killed when the thing is removed (line 119).