Dynamic Link Libraries for DOS

DLLs provide an easy way to run large programs in small memory spaces and Gary shows how you can add DLL facilities to DOS programs.


May 01, 1990
URL:http://www.drdobbs.com/windows/dynamic-link-libraries-for-dos/184408345

Figure 1

MAY90: DYNAMIC LINK LIBRARIES FOR DOS

DYNAMIC LINK LIBRARIES FOR DOS

Running large programs in small memory space

Gary Syck

Gary is an independent consultant and can be reached at 12032 100th Ave. N.E., #D203, Kirkland, WA 98034.


With the average size of available memory increasing, the average size of applications that use that memory also grows, as programmers come up with new ways to put all that memory to use. The real challenge is to shoehorn the great ideas for tomorrow's programs into the limited memory of today's computers. At some point you just can't shrink programs without leaving out attractive features. Of course, you can try to convince users that they don't need their favorite features, but a better strategy is to come up with some kind of memory management that lets you move portions of the program and data in and out of memory as required.

Most PC programming systems provide some way to do overlays, which are portions of a program that load into the same area of memory. When a routine is required, that routine gets loaded into the overlay area, replacing whatever code or data is already there.

Things get complicated when the routine in the overlay area requires a routine that is in another overlay. In this case, the calling routine will be swapped out. This causes problems when the called routine returns and finds something else where the calling routine used to be.

To prevent this problem when laying out the overlay areas for a program, you must consider what routines are called from where. Figure 1 shows a typical memory map for a program that uses overlays.

Enter Dynamic Link Libraries

Dynamic link libraries (DLLs), which were introduced with Windows and OS/2, move the task of linking in code from the end of the compile/link step to when the program is actually running. Run-time linking lets the linker put an object file anywhere in available memory. If there is no memory available, the linker can find an object file that is not being used and replace it with the file it needs to load. But to take advantage of DLLs, you must use an operating system that supports them, such as Windows or OS/2. (It is ironic that to make use of a system that makes large programs run in small memory spaces, you must use an operating system that requires several megabytes of RAM to work.)

The routines presented in this article show how to make a run-time linker/loader that uses about 5K of code. This is the heart of a DLL system. At the end of the article are some comments on how this can be expanded into a full DLL system.

The key to understanding what is going on is to remember that a DLL loader is simply a linker that works at run time, combined with a program loader. A conventional linker combines all of the object files for a program into a single file and fixes all of the memory references to point to locations in the file. The loader puts the file in memory, adds the address where the file was loaded to the memory addresses, sets up the segment registers, and jumps to the start address of the program.

I made some assumptions in order to simplify the process of linking and loading object files. The basic assumption is that the object file was created by Microsoft C 5.1, using the large memory model. This choice of language and memory model means that the linker loader will always deal with the same arrangement of segments.

There are three major pieces to this program. The first piece, GETDATA.C (Listing One, page 104), loads all of the global data and creates the master symbol table. The second, STUB.ASM (Listing Two, page 104), is the calling mechanism for functions. LINKER.C (Listing Three, page 105) is the linker itself. Finally, the file DLL.C (Listing Four, page 108) ties them all together.

Loading Global Data

Global data is a bit of a problem for the run-time linker. All global data must be defined before it can be referenced. With conventional linkers this is not a problem because the linker reads all of the object files before doing any fix-ups. The run-time linker looks at one module at a time, so modules that define data must be loaded before modules that use the data.

Another problem with global data occurs when the linker wants to remove a module from memory. Ideally, it should also remove any static data for that module. There is, unfortunately, no way to tell the difference between static data and initialized global data.

The solution to these problems is to require all global data to be defined in a single object file. Loading this object file first insures that global data will be ready when it is needed. Any global data that is not in this file is assumed to be static data associated with the module being loaded. The file used in this sample program is named GBLDATA.OBJ, which is a standard object file that is created by compiling the C source file, GBLDATA.C (Listing Five, page 108), which contains global data definitions. Listing Six (page 109) is the file MAIN.C from which MAIN.OBJ is created; Listing Seven (page 109) is DLL.H, the header file.

The function GetData in GETDATA.C reads GBLDATA.OBJ and extracts the data information in it. The function begins by opening the object file. Next, a loop reads each object record from the object file. In the loop is a switch statement that does different things to different record types. When the loop is finished, the linker closes the file and allocates space for the last group of uninitialized data variables.

Two types of variables can be expected in GBLDATA.OBJ. The first type is initialized data. In this implementation all initialized data goes in the near data segment in the DataSpace array defined in STUB.ASM.

The PUBDEF record in the object file contains all of the names of the initialized data variables. This information, along with the size and location of the data, is copied into the Syms table. The data that initializes the variables is in the LEDATA records.

The second type of variable is the uninitialized data. The names of uninitialized variables can be found in COMDEF records. The COMDEF record contains the name, number of elements, and size of an element for a variable. This information is copied into the Syms table. To reduce the number of allocations required, no allocation is made until there is at least 32K of data to allocate. The function AllocateSyms does the allocation and puts the appropriate addresses into the Syms table entries for the symbols affected. A final allocation must be done when there are no more records to pick up the last group of variables.

Calling Functions

When GetData has loaded all the global data, the main routine moves on to functions. Function information is kept in a table called "FuncLst." Library functions must be placed in the table explicitly (unless you have the object files for the library available). The library functions will be linked to the main program by the conventional linker. In this example the library function printf will be used, so it must be placed in the table.

All other functions will be loaded by the run-time linker. Before it can be loaded, a function must have an entry in the FuncLst table. The entry contains the name of the function, the location of the function (or NULL if the function is not loaded, yet), a flag that tells if the function is unloadable, and a copy of the stub routine. The entry after all of the library routines is for the first function in the program. Note that the address of the function is NULL. This indicates that the function has not been loaded yet. To start the program the stub routine for this function is called.

The stub routine is a copy of the function Stub in STUB.ASM. Each copy is modified so that it knows what function it goes with. The stub routine will be called instead of the related function. The job of the stub routine is to determine if the function is in memory (by the value of the address in FuncLst) and load it if it is not. In a system that swaps functions in and out of memory, the stub routine would also set flags indicating that the function is in use or not. Once the function has been loaded, the stub routine jumps to the function.

Linking Modules

When the function is not in memory, the stub routine calls the linker. The first step is to find the file. The name of the file is extracted from the name of the function. If there is only one file, the name of the function must match the name of the file. If there are several functions, they each begin with the name of the file, followed by an underscore, then the function name.

The logic for loading the file into memory is similar to that used for loading GBLDATA.OBJ. The difference is that there are a few more records to deal with. The first new record is the EXTDEF record. This record contains the external symbols used by the module. If the external symbol is in Syms, then it is a data reference; otherwise, it is another function, and an entry is made in FuncLst. Each external item is put in a table called "ExtSyms." This table maps the symbol number used in this file to the symbol numbers used in Syms and FuncLst.

More Details.

The other new record is the FIXUP. These records are used to put the addresses of functions and data into the code in the module. Each fix-up refers to an item in Syms or FuncLst (via ExtSyms). The fix-up code looks up the address required and places it in the code at the location specified.

Two of the record types used in GBLDATA.OBJ have a slightly different purpose in the linker. The first is the PUBDEF record. The only public symbols in these files should be function names. The PUBLIC record is the only place to get the offsets of the functions in this module. This information will be copied into FuncLst when the code for the function is loaded.

The other record that is different is the LEDATA record. In these files the LEDATA record contains either initialized data or code. The segment field is used to tell the difference. Segments are referred to by number in LEDATA records. The number points to a name in the SEGDEF record. Because Microsoft C always uses the same segments, the numbers don't change -- so the SEGDEF record does not have to be used. LEDATA records for segment 2 are string literals; segment 3, constants; and segment 1, code.

The string literals and constants are placed in the data segment. The variables DataSize and LocalSize are the start of the local data and the size of the local data. These variables can be used to remove the local data from memory when the function gets swapped out.

The linker allocates space for all of the code segment LEDATA records found. Once the space is allocated, the address of the space and the offset from the PUBDEF record are combined to make the address of the function. This information is then placed in the FuncLst table for use by that function's stub routine.

When there are no more records, the linker closes the object file and updates the DataSize pointer. The linker returns control to the stub routine. The stub routine jumps to the function. It jumps instead of calling the function so that the stack will not contain an extra return address. In a fully functioning DLL system the stub routine would pop the return address off the stack and then call the function. When the function returned, the stub routine could adjust any flags required and return to the address it popped off the stack.

Summary

DLLs provide an easy-to-use, flexible system for running large programs in small memory spaces. If history is any guide, then there will always be a need for this kind of technique. The implementation of a DLL system is not very complex. You do not need to have a sophisticated operating system to implement one.

The program presented here does the hard part -- linking the functions. To make it a complete DLL system you need a memory management system that keeps track of which functions are active and which can be removed from memory.

The program assumes that everything is correct. It will crash if there are undefined symbols or missing object files. This could be corrected with a preprocessor that verifies that all of the pieces are correct. This preprocessor could also put the object files in a single file for easier control.

Using DLLs can change the way you look at programming problems. If the modules are designed well, the same module can be used by many programs. Changing the module changes all of the programs without having to relink every program. Users can customize their software in their own favorite language without having access to proprietary portions of the main program.

Try some experiments with DLLs. You will find your own interesting uses for them, and may well eliminate complaints about how large your programs have grown.

Compiler Supported DLLs for DOS

Andrew Schulman

Andrew Schulman is a contributing editor to Dr. Dobb's Journal and is a co-author of the book, Extending DOS (Addison-Wesley, 1990).

The main executable for Jensen & Partners International's (JPI) new TopSpeed C development environment, TS.EXE, is only 7K bytes in size. TSC.EXE, the command-line version of the C compiler, is also only 7K. The only .OVL (overlay) file is 38K. So where's the code?

Regrettably, JPI has not returned us to the days of Turbo Pascal 3.0, when a blazingly fast compiler and full-screen text editor fit in under 40K. Instead, most of the JPI environment is contained in files such as TSMAIN.DLL, TSLINK.DLL, TSMAKE.DLL, and TSASM.DLL.

Microsoft's EXEHDR utility (included with the Windows and OS/2 software development kits) reveals that these are "true" segmented-executable dynamic-link libraries (DLLs). The entry points have names such as STR$CARDTOSTR and WINDOWS$PUTONTOP, corresponding to functions from JPI's Modula-2 compiler (for example, Windows. PutOnTop( )). This C compiler and development system has, with the exception of library functions such as printf( ), been written not in C, but in Modula-2.

JPI's use of DLLs for DOS makes sense for a number of reasons:

Purchasers of the TopSpeed C Extended Edition (which costs $200 more than the Standard Edition) get to use the same DOS DLL technology in their own programs. The Extended Edition includes complete source code for JPI's implementation of DOS DLLs.

In contrast to the Microsoft tools, no .DEF file is required to make a DLL in the JPI environment. Instead, you place a directive such as "make DOS DLL," "make OS2 DLL," or "make WIN DLL" (for Microsoft Windows) in a project file. To use DLLs in an MS-DOS program, you use the directive "make DynaEXE."

Analogous to the LIBPATH statement in an OS/2 CONFIG.SYS file, when using JPI DLLs under MS-DOS, you need to set a LIBPATH DOS environment variable.

JPI appears to have given far greater thought than Microsoft to issues of C language support for dynamic linking and multi-threaded programming. For example, the JPI license agreement contains a well thought-out statement regarding distribution of DLLs. Similarly, each function in JPI's Library Reference manual contains a discussion of "Multi-thread considerations." This is connected with dynamic linking, because all DLLs, and all programs that call DLLs, must use JPI's "MThread" model.

It was said earlier that JPI's DOS DLLs are like transparent overlays. When a feature is said to be transparent, it generally means that (to the programmer) it looks like the feature isn't there. However, transparency isn't always useful. In the case of dynamic linking, sometimes the programmer needs explicit control over this process (see my article "Linking While the Program is Running," DDJ, November 1989). Unfortunately, JPI's DOS implementation currently does not provide any way to dynamically link under explicit program control, in that there is no DOS equivalent to the OS/2 functions DosLoadModule( ) and DosGetProcAddr( ). In another sense, though, JPI's implementation of DOS DLLs is currently not transparent enough: You will notice a performance penalty. In one test, a program that called code in a DLL took four times as long to run as the fatter "stand-alone" version that didn't use DLLs. However, JPI claims this was a worst-case example, and that typical DLL use presents only a 10-15 percent performance penalty.

JPI is not alone in bringing DLLs to DOS: Several other companies are working towards the same goal. First and foremost a Modula-2 company, JPI, like other Modula-2 companies such as Stony Brook, is also working to ensure that multi-threaded programming is portable between OS/2 and MS-DOS (multiprocessing is a fundamental part of the Modula-2 programming language). By making DLLs and multi-threaded programming available to MS-DOS C programmers, JPI is helping to bridge the gap between MS-DOS and OS/2.

This is part of a general trend toward making OS/2 features available under MS-DOS. Protected-mode DOS extenders are another example of this trend: They provide a large address space, virtual memory, and protected mode, while still holding onto the venerable MS-DOS operating system. This, like the goal of dynamic linking under MS-DOS, in turn reflects the human instinct to "have your cake and eat it too."


_DLLs FOR DOS_ by Gary Syck

[LISTING ONE]



// GETDATA.C   Read GBLDATA.OBJ to get global variables for DLLs
//   02/12/89   by Gary Syck
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include "dll.h"

// Open GBLDATA and read data
void
GetData()
{
   int fd;            // File descriptor for GBLDATA.OBJ
   int   Idx;         // Index numbers from the file
   unsigned long Elmnts, DSize;   // Number and size of global data items
   unsigned PubGrp, PubSeg;   // Group and segment indexes for data
   unsigned DOff;         // Offset for initialized data
   unsigned char type;      // type of record read
   int   size;         // size of the record
   unsigned char *Data;      // the data to read
   unsigned char *DSptr;      // Pointer to the Data segment
   int i, j;

   if((fd=open( "gbldata.obj", O_BINARY|O_RDONLY )) == -1 )
   {
      printf( "Unable to open file\n" );
      exit(1);
   }
   DataSize = 0;
   SymCnt = 0;
   AllocNumb = 0;
   SymSize = 0;
   type = 0;
   DSptr = &DataSpace;
   while( type != MODEND )
   {
      read( fd, (char *) &type, sizeof( unsigned char ));
      read( fd, (char *) &size, sizeof( int ));
      Data = malloc( size );
      read( fd, Data, size );
      switch( type )
      {
         case PUBDEF:   // The record contains public symbols
            i=0;
            if( Data[i]&0x80 )
            {
               PubGrp = (Data[i++]&0x7f)<<8;
               PubGrp += Data[i++];
            }
            else
               PubGrp = Data[i++];
            if( Data[i]&0x80 )
            {
               PubSeg = (Data[i++]&0x7f)<<8;
               PubSeg += Data[i++];
            }
            else
               PubSeg = Data[i++];
            if( PubSeg == 0 )
               i += 2;    // skip the frame number
            AllocateSyms();   // make memory for all symbols
            /* assume all public defs are in the DGROUP */
            while( i<size-1 )
            {
              Syms[SymCnt].Name = malloc( Data[i] + 2 );
              strncpy( Syms[SymCnt].Name, &Data[i+1], Data[i] );
              Syms[SymCnt].Name[Data[i]] = '\0';
              i += Data[i]+1;
              Syms[SymCnt].Seg = FP_SEG(DSptr);
              Syms[SymCnt].Offset = (FP_OFF(DSptr))+ *((int *) &Data[i] );
              SymCnt++;
              i += 2;
              if( Data[i]&0x80 ) // skip over the type
              {
                  Idx = (Data[i++]&0x7f)<<8;
                  Idx += Data[i++];
              }
               else
                  Idx = Data[i++];
            }
            break;
         case LEDATA: // record contains data for data segment
            i = 0;
            if( Data[i]&0x80 )
            {
               Idx = (Data[i++]&0x7f)<<8;
               Idx += Data[i++];
            }
            else
               Idx = Data[i++];
            /* Assume all data is for the data segment */
            DOff = *((int *) &Data[i] );
            i += 2;
            memcpy( &DSptr[DOff], &Data[i], size-(i+1) );
            if( DataSize < DOff + size - (i+1))
               DataSize = DOff + size - (i+1);
            break;
         case COMDEF:  // record contains uninitialized data
            i = 0;
            while( i < size-1 )
            {
             Syms[SymCnt].Name = malloc( Data[i]+2 );
             strncpy( Syms[SymCnt].Name, &Data[i+1], Data[i] );
             Syms[SymCnt].Name[Data[i]] = '\0';
             i += Data[i] + 2;
             if( Data[i++] == 0x61 )
            {
               if( Data[i] < 128 )
                 Elmnts = (unsigned long) Data[i++];
                 else
                 {
                 j = Data[i++] - 127;
                 Elmnts = 0L;
                 memcpy( &Elmnts, &Data[i], j );
                 i += j;
                 }
                if( Data[i] < 128 )
                DSize = (unsigned long) Data[i++];
                else
                {
                j = Data[i++] - 127;
                DSize = 0L;
                memcpy( &DSize, &Data[i], j );
                i += j;
                }
                Syms[SymCnt].Size = (unsigned) (Elmnts * DSize);
                if( (unsigned long) SymSize + (unsigned long) (Elmnts * DSize) >= 32000L )
                AllocateSyms();
                SymSize += (unsigned) (Elmnts * DSize);
                SymCnt++;
               }
            }
            break;
         default:
            break;
      }
      free( Data );
   }
   close( fd );
   AllocateSyms();   // make memory for all of the symbols
}

// make a memory block to hold the previous symbols
void
AllocateSyms()
{
   char *Buff;
   unsigned Seg, Off;
   if( SymSize )
   {
      Buff = malloc( SymSize );
      Seg = FP_SEG( Buff );
      Off = FP_OFF( Buff );
      SymSize = 0;
      while( AllocNumb < SymCnt )
      {
         Syms[AllocNumb].Seg = Seg;
         Syms[AllocNumb].Offset = Off;
         Off += Syms[AllocNumb].Size;
         AllocNumb++;
      }
   }
}




[LISTING TWO]


; STUB.ASM  Used by DLL to see if a function needs to be loaded.
; 02/12/89  By Gary Syck

   TITLE   stub.asm
   NAME    stub

   .8087
STUB_TEXT   SEGMENT  WORD PUBLIC 'CODE'
STUB_TEXT   ENDS
_DATA   SEGMENT  WORD PUBLIC 'DATA'
_DATA   ENDS
CONST   SEGMENT  WORD PUBLIC 'CONST'
CONST   ENDS
_BSS   SEGMENT  WORD PUBLIC 'BSS'
_BSS   ENDS
DGROUP   GROUP   CONST, _BSS, _DATA
   ASSUME  CS: STUB_TEXT, DS: DGROUP, SS: DGROUP
EXTRN   _LoadFunc:FAR      ; Function to get a function into memory
EXTRN   _FuncLst:DWORD      ; The function table
_DATA   SEGMENT
   PUBLIC   _DataSpace
_DataSpace   db 32000 dup(?)
_DATA   ENDS
STUB_TEXT      SEGMENT
   ASSUME   CS: STUB_TEXT
   PUBLIC   _Stub
_Stub   PROC FAR
   mov   bx,offset _FuncLst   ; this is modified before use
   mov   ax,seg _FuncLst
   mov   es,ax
   mov   ax,WORD PTR es:[bx+4]   ; check .Loc
   or   ax,WORD PTR es:[bx+6]
   jne   noload
   push   es         ; save ES and BX
   push   bx
   call   FAR PTR _LoadFunc
   pop   bx
   pop   es
noload:
   jmp   DWORD PTR es:[bx+4]   ; go to the function
_Stub   ENDP
STUB_TEXT   ENDS
END





[LISTING THREE]


// LINKER.C Link a module at run time
// 02/12/89 by Gary Syck
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <stdlib.h>
#include "dll.h"

// Load the function in the FUNCTAB entry
void
LoadFunc( FUNCTAB *func )
{
   char FileName[15], *p, Name[80];
   int   fd;
   unsigned char type;
   int size;
   unsigned char *Data, *Dp, *LastFunc;
   int i, j, ln;
   unsigned pubgrp, pubseg, dataseg;
   unsigned DROff, Loc, FixDat;
   unsigned FrameIdx, TargetIdx, TargetDisp, tmp;
   unsigned long ltmp;
   int dataoff;
   unsigned LocalSize;   // size of the data allocated here
   struct {
      int   Flag;      // 1 = data, 2 = function
      int SymNumb;   // What symbol
   } ExtSyms[20];
   int ExtCnt;
   struct {
      int FuncNumb;
      int OffSet;
   } Pubs[10];
   int PubCnt;
   struct {
      unsigned Meth;   // The method to use
      unsigned Idx;   // The index
   } Threads[8];
   void *ptr;
   unsigned char *DSptr;
   unsigned constseg;   // Where constants begin

   strncpy( FileName, &func->Name[1], 14 );
   FileName[14] = '\0';
   if((p=strchr( FileName, '#' )) != NULL )
      *p = '\0';
   strcat( FileName, ".obj" );
   if((fd=open( FileName, O_RDONLY|O_BINARY )) == -1 )
   {
      printf( "Unable to open file: %s\n", FileName );
      return;
   }
   LocalSize = 0;
   ExtCnt = 0;
   PubCnt = 0;
   type = 0;
   DSptr = &DataSpace;
   while( type != MODEND )
   {
      read( fd, (char *) &type, sizeof( unsigned char ));
      read( fd, (char *) &size, sizeof( int ));
      Data = malloc( size );
      read( fd, Data, size );
      switch( type )
      {
         case THEADR:   // ignore the header
         case COMMENT:   // ignore comments
         case GRPDEF:   // these are always the same
         case MODEND:
         case SEGDEF:
         case LNAMES:
            break;
         case EXTDEF:   // find external names
            for( i=0; i<size-1; i += ln+2 )
            {
              ln = Data[i];
              if( ln )
              {
               strncpy( Name, &Data[i+1], ln );
               Name[ln] = '\0';
               for( j=0; j<SymCnt && strcmp(Syms[j].Name, Name );j++);
               if( j<SymCnt )
               {
               ExtSyms[ExtCnt].Flag = 1;
               ExtSyms[ExtCnt].SymNumb = j;
               }
               else
               {
               for( j=0; j<FuncCnt
               && strcmp( FuncLst[j].Name,Name );j++);
               if( j>=FuncCnt ) // make space for it
                   {
                   FuncLst[FuncCnt].Name =
                                                       malloc(strlen(Name)+2 );
                strcpy( FuncLst[FuncCnt].Name, Name );
               FuncLst[FuncCnt].Loc = NULL;
               FuncLst[FuncCnt].Flag = 0;
               memcpy( FuncLst[FuncCnt].Stub, Stub, STUBSIZE );
               memcpy(&FuncLst[FuncCnt].Stub[4],&FuncLst[FuncCnt],2);FuncCnt++;
                  }
                  ExtSyms[ExtCnt].Flag = 2;
                  ExtSyms[ExtCnt].SymNumb = j;
               }
               ExtCnt++;
               }
            }
            break;
         case PUBDEF:   // add to list of available functions
            i = 0;
            if( Data[i]&0x80 )
            {
               pubgrp = (Data[i++]&0x7f) << 8;
               pubgrp += Data[i++];
            }
            else
               pubgrp = Data[i++];
            if( Data[i]&0x80 )
            {
               pubseg = (Data[i++]&0x7f) << 8;
               pubseg += Data[i++];
            }
            else
               pubseg = Data[i++];
            if( pubseg == 0 )   // skip the frame
               i += 2;
            while( i < size-1 )
            {
               ln = Data[i];
               if( ln )
               {
                    strncpy( Name, &Data[i+1], ln );
                    Name[ln] = '\0';
               }
               i += ln + 1;
               memcpy( &ln, &Data[i], sizeof( int ));
               i += 2;
               if( Data[i]&0x80 )
                  i += 2;
               else
                  i++;
               for( j=0; j<FuncCnt
                    && strcmp( FuncLst[j].Name, Name ); j++ );
               Pubs[PubCnt].FuncNumb = j;
               Pubs[PubCnt].OffSet = ln;
               PubCnt++;
            }
            break;
         case LEDATA:
            i = 0;
            if( Data[i]&0x80 )
            {
               dataseg = (Data[i++]&0x7f) << 8;
               dataseg += Data[i++];
            }
            else
               dataseg = Data[i++];
            memcpy( &dataoff, &Data[i], sizeof( int ));
            i += sizeof( int );
            if( dataseg == 2 ) // it's for the data segment
            {
               Dp = &DSptr[DataSize+dataoff];
               memcpy( Dp, &Data[i], size - ( i+1 ));
               if( LocalSize < dataoff + size - (i+1))
                   LocalSize = dataoff + size - (i+1);
               constseg = LocalSize+DataSize;
            }
            else if( dataseg == 3 ) // for const segment
            {
               Dp = &DSptr[constseg+dataoff];
               memcpy( Dp, &Data[i], size - ( i+1 ));
               if( LocalSize < dataoff + size - (i+1))
                   LocalSize = dataoff + size - (i+1);
            }
            else   // here is the code
            {
               if( dataoff == 0 ) LastFunc = malloc( size - (i+1) );
               else
                 LastFunc = realloc( LastFunc, size - (i+1) + dataoff );
               Dp = &LastFunc[dataoff];
               memcpy( Dp, &Data[i], size-(i+1));
               for( j=0; j<PubCnt; j++ )
               {
                  FuncLst[Pubs[j].FuncNumb].Loc = &LastFunc[Pubs[j].OffSet];
               }
            }
            break;
         case FIXUPP: // only look at the fixup fields all
                                     // threads are the same.
            i = 0;
            while( i < size-1 )
            {
               if( (Data[i])&0x80 )   // its a fixup
               {
                DROff = ((Data[i]&3)<<8) + Data[i+1];
                Loc = Data[i];
                i += 2;
                FixDat = Data[i++];
                FrameIdx = TargetIdx = TargetDisp = 0;
                if( !(FixDat&0x80) && (FixDat&0x70) != 0x50 )
                  /* there is a frame index */
                {
                  if( Data[i]&0x80 )
                  {
                     FrameIdx = (Data[i++]&0x7f) << 8;
                     FrameIdx += Data[i++];
               }
                  else
                     FrameIdx = Data[i++];
               }
               if( !(FixDat&8) ) /* thread index */
               {
               if( Data[i]&0x80 )
               {
               TargetIdx = (Data[i++]&0x7f) << 8;
                          TargetIdx += Data[i++];
               }
               else
               TargetIdx = Data[i++];
               }
               if( !(FixDat&4) )
               {
               memcpy( &TargetDisp, &Data[i], sizeof( int ));
                   i += 2;
               }
               /* fix up FixDat from threads */
               if( FixDat&0x80 ) // frame from thread
               {
                   j = ((FixDat&0x70)>>4) + 4;
                   tmp = Threads[j].Meth << 4;
                   FixDat = (FixDat&0xf) | tmp;
                      FrameIdx = Threads[j].Idx;
               }
               if( FixDat&8 ) // target from a thread
               {
                  j = FixDat&3;
                  tmp = Threads[j].Meth&3;
                  FixDat = (FixDat&0xf4) | tmp;
                  TargetIdx = Threads[j].Idx;
               }
               switch( Loc&0x1C ) // find what we need
               {
               case 0x4:   // offset fixup
                  if( (FixDat&7) == 4 )
                  {
                  /* get the value to be fixed */
                  memcpy( &tmp, &Dp[DROff], sizeof(int));
                     if( TargetIdx == 2 ) // data seg
                         {
                     tmp += ((unsigned long)
                        &DSptr[DataSize])&0xffff;
                     }
                  else if( TargetIdx == 3 )
                                                       {
                     tmp += ((unsigned long)
                       &DSptr[constseg])&0xffff;
                     }
                     /* put the fixed number back */
                     memcpy( &Dp[DROff], &tmp,
                                                                  sizeof(int));
                     }
                  else if((FixDat&7) == 6 )
                     {
                  if( !(Loc&0x40) )
                    {
                    ltmp = (unsigned long)
                    (FuncLst[ExtSyms[TargetIdx-1]
                     .SymNumb].Loc);
                     ltmp -= (unsigned long) (&Dp[DROff])+2L;
                     memcpy( &Dp[DROff],<mp, sizeof(int));
                      }
                       else // put the offset in
                      {
                      memcpy( &Dp[DROff],&Syms[ExtSyms[TargetIdx-1].SymNumb].Offset, sizeof( int ));
                       }
                  }
                  break;
                  case 0x8:  // segment fixup
                  if( (FixDat&7) == 6 )
                  {
                  memcpy( &Dp[DROff], &Syms[ExtSyms[TargetIdx-1].SymNumb].Seg, sizeof( int ));
                  }
                  break;
                  case 0xC: // symbol from target
                  if( (FixDat&7) == 6 )
                  {
                  ptr = FuncLst[ExtSyms
                                                   [TargetIdx-1].SymNumb].Stub;
                     memcpy( &Dp[DROff], &ptr,
                                                              sizeof( void *));
                   }
                  break;
                  }
               }
               else   // its a thread
               {
                  j = Data[i]&3;
                  if( Data[i]&0x40 )
                     j += 4;
                       Threads[j].Meth = (Data[i]&0x1C)>>2;
                  i++;
                  if( Data[i]&0x80 )
                  {
                  tmp = (Data[i++]&0x7f) << 8;
                  tmp += Data[i++];
                  }
                   else
                  tmp = Data[i++];
                   Threads[j].Idx = tmp;
               }
            }
            break;
         default:
          printf( "invalid record: %x size: %d\n", type, size );
          break;
      }
      free( Data );
   }
   close( fd );
   DataSize += LocalSize;
}





[LISTING FOUR]


// DLL.C Implement DLLs for DOS
// 02/12/89 by Gary Syck
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAIN
#include "dll.h"

void
main( int argc, char *argv[] )
{
   void (*func)(void);
   unsigned char *ptr;

   GetData();            // Read the global data
   FuncCnt = 2;            // make two functions
   FuncLst[0].Name = malloc( 10 );
   strcpy( FuncLst[0].Name, "_printf" );   // The library function printf
   FuncLst[0].Loc = printf;
   FuncLst[0].Flag = 1;
   memcpy( FuncLst[0].Stub, &Stub, STUBSIZE );
   ptr = FuncLst;
   memcpy( &FuncLst[0].Stub[1], &ptr, 2 );

   FuncLst[1].Name = malloc( 10 );
   strcpy( FuncLst[1].Name, "_main" );       // The first function to run
   FuncLst[1].Loc = NULL;
   FuncLst[1].Flag = 0;
   memcpy( FuncLst[1].Stub, &Stub, STUBSIZE );
   ptr = &FuncLst[1];
   memcpy( &FuncLst[1].Stub[1], &ptr, 2 );
   func = FuncLst[1].Stub;
   (*func)();   // Call the main function
}





[LISTING FIVE]


// Global data to be used with sample DLL

// make various data items

char Str[] = "Tester equals";
int Flubber=20;
int Tester=14;





[LISTING SIX]


// sample DLL routine
#include <stdio.h>

void main(void);
void Test(void);

extern int Flubber;
extern char Str[];
extern int Tester;

// print hello world and the value of Flubber then call Test
void
main()
{
   printf( "Hello, world: %d\n", Flubber );
   Test();
}

// print a message and the values of Str and Tester
void
Test()
{
   printf( "This is a test function\n%s: %d\n", Str, Tester );
}




[LISTING SEVEN]


// DLL.H  Include file for DLL
// 02/12/89 By Gary Syck

#ifdef MAIN
#define EXTERN
#else
#define EXTERN extern
#endif

typedef struct {
   char       *Name;      // Name of the symbol
   unsigned   Size;      // size of the symbol
   unsigned   Seg;      // Segment containing the symbol
   unsigned   Offset;      // Offset for the symbol
} SYMTAB;

#define STUBSIZE 50
typedef struct {
   char      *Name;      // name of the function
   void      *Loc;      // where the function is stored
   int         Flag;   // true if function is executing
   unsigned char Stub[STUBSIZE];   // Function to call this function
} FUNCTAB;

SYMTAB Syms[200];         // the symbols found
EXTERN int   SymCnt;         // The number of symbols
EXTERN int   AllocNumb;      // The first symbol in the current
               // allocation block
EXTERN SYMTAB   Syms[];         // list of symbols
EXTERN int SymCnt;         // number of symbols
EXTERN unsigned SymSize;      // running total of the size of stuff
EXTERN int   DataSize;      // how many bytes in DataSpace are used

EXTERN FUNCTAB FuncLst[100];      // the list of functions
EXTERN int    FuncCnt;      // The number of allocated entries

extern   unsigned char DataSpace;   // room in the data segment
extern char Stub;         // This is really a function. It is
                                        // used this way to make sure the
                                        // segment gets used in memcpy
/* record types */
#define THEADR      0x80
#define COMMENT   0x88
#define MODEND      0x8A
#define EXTDEF      0x8C
#define TYPDEF      0x8E
#define PUBDEF      0x90
#define LINNUM      0x94
#define LNAMES      0x96
#define SEGDEF      0x98
#define GRPDEF      0x9A
#define FIXUPP      0x9C
#define LEDATA      0xA0
#define LIDATA      0xA2
#define COMDEF      0xB0

// Function prototypes
void main( int argc, char *argv[] );
void LoadFunc( FUNCTAB *Func );
void GetData(void);
void AllocateSyms(void);









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.