Dynamic Link Libraries


Introduction

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).

How to use DLL's

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.

Mixing c68 libraries and DLL's

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. Error LinkDLL(); Of course, you also have to make sure that this function is included in your program. This can be done by including LinkDLL_rel in the list of files which have to be linked.

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 DLL's

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).


PROGS, Professional & Graphical Software
last edited February 18, 1995