A bare-bones DLL in C and a program that uses it
line.jpg (990 bytes)

Here is some C code for a 100% barebones, do-nothing DLL.  The code is well commented, so check it out.  The entire source code is available in text (compilable) and html.  In this tutorial, however, we will only be going over the DLL source and a snippet of the DLLUser application.  The code to the DLLUser application is well commented, and most likely very interesting if you want to see what a Windows program looks like, but is out of the scope of this tutorial.

First we will look at the DLL's entrypoint function, DllEntryPoint(). Windows calls this function when the DLL is first loaded, when another process attaches to/detaches from the DLL (says it will use the DLL), and when an attached process creates/destroys a thread.

Windows lets the DLL know why DllEntryPoint() is being called via the fdwReason parameter. In our DllEntryPoint(), we check to see what the dwReason parameter is and take action accordingly.

 

/*
  DllEntryPoint

  Preconditions: none - it is called at various times during the DLL's
                 life (when first loaded, when processes attach/detach,
                 etc).

  PostConditions: none - this implementation of DllEntryPoint doesn't
                  really do anything.  Normally, this function would
                  look at the fdwReason parameter and allocate/free
                  memory depending on what fdwReason is.

  Parameters:
    HINSTANCE hDLL:  The HINSTANCE of this dll.  You save this variable
                     and pass it to Windows API functions that require
                     an HINSTANCE parameter.

    DWORD fdwReason: This is a numeric code that tells us why Windows
                     is calling DllEntryPoint()

    LPVOID lpvReserved: This parameter is not used - it is reserved
                        by Windows for a possible future use.  Microsoft
                        likes to do this stuff sometimes...

  Return value:  Returns TRUE if initialization completes succesfully,
                 It returns FALSE if something goes wrong and the DLL
                 is unable to initialize.

*/

BOOL WINAPI DllEntryPoint( HINSTANCE hDll,
                           DWORD     fdwReason,
                           LPVOID    lpvReserved )
{

  /*
    Let's find out exactly why Windows called us...
  */

  switch ( fdwReason )
  {
    case DLL_PROCESS_ATTACH:

      /*
        Here we would normally do some startup stuff, like saving the
        hDll parameter to a global variable, allocating memory for
        local structures and stuff, etc...
      */

      break ;

    case DLL_PROCESS_DETACH:

      /*
        Here we would free the memory and stuff we allocated in
        response to DLL_PROCESS_ATTACH.
      */

      break ;

    default :

      /*
        If fdwReason isn't the above two cases, we'll just ignore it
      */

      break ;
  };
 

There is only one other function in basicdll.dll, and that is our one exported function. It is aptly named ExportedFunction().   Exported function doesn't do anything really, it just displays a message box that informs the user that it has been called properly.

 

/*
  ExportedFunction

  Preconditions: none
  Postcontitions: none

  Parameters:
    none

*/

int WINAPI ExportedFunction( void )
{

  /*
    Let's call MessageBox() to display a nice friendly notification
    that we were called.  Another program or DLL can call this function
    by either linking to this DLL at compile time, or calling
    LoadLibrary() and GetProcAddress().
  */


  MessageBox( NULL,  //HWINDOW of the window that owns the message box
              "Hello from inside basicdll.dll!" , //Text
              "ExportedFunction() has been called!" ,//Title
              MB_OK | MB_ICONINFORMATION );//Bit mask flags

  return 1;
}

 

Next we are going to take a look at the DllUser.exe code that calls ExportedFunction() from our DLL.  I'm only going to go over the code that calls Exported Function, and the .DEF file that is linked with DllUser.exe .

The code that we are looking at is inside DllUser.exe's message loop, which is the function in Windows programs that is notified of user actions.   DllUser calls ExportedFunction() in two ways - it calls it like "normal", as it is able to do because it has linked with the DEF file that tells the linker where ExportedFunction() is.  It will also explicitly load basicdll.dll into memory and use GetProcAddress() (a WindowsAPI function) to get the address of ExportedFunction() in our DLL.   It then uses a function pointer to call ExportedFunction().  As you can see when you download the sourcecode, both methods work.

 

                case CM_USENORMAL:

                    // We linked to the function at compile time,
                    // so let's just call it
                    //
                    ExportedFunction();

                    break ;

                case CM_USEFP:
                    // First let's load the DLL
                    //
                    hDll = LoadLibrary("basicdll.dll" );

                    // On error, we need to return
                    //
                    if ( !hDll )
                      return 0;

                    // Now let's get a pointer to ExportedFunction
                    //
                    funcPtr = (LPFNEXPORTED)
                               GetProcAddress( hDll,
 "ExportedFunction"  );

                    // If funcPtr is null, we need to return
                    //
                    if ( !funcPtr )
                      return 0;

                    // Now let's call ExportedFunction!  Notice how you
                    // use the name of the function pointer just like
                    // you would use the actual function name.
                    //
                    funcPtr();

                    // Now we're done with basicdll.dll, so let's unload
                    // it.
                    //
                    FreeLibrary( hDll );

                    break ;
 

Now let's look at the DEF file that DllUser.exe is linked with.  The code is very well commented, as you can see, and should be relatively self explanatory.   Notice that by listing ExportedFunction() in our DEF file, we are able to call it without doing any special preparation in our code.

 

;In this section we define what functions we import from other DLL's
;Once the linker has this info, we can use the function in our EXE
;without calling LoadLibrary() and GetProcAddress(), although the DLL
;will be loaded the entire time our EXE is running.
;
;You can also link to functions in DLL's at compile time by a bunch of
;other methods.  You can link with .lib files, specifically declare
;functions as imported, and other stuff like that.  Most of these ways
;introduce some compiler dependence (for example: BorlandC++ can't use
;.lib files compiled by VisualC++), but most problems like these have
;relatively minor workarounds.  Check your particular compiler's
;documentation to figure out which way would be best for you.  This
;method should always work with any compiler.

IMPORTS

; The syntax for declaring a function to be imported from a DLL:
; [internal name=]module.entry
;
; internal name: The name that your EXE will use to call the imported
;                 function.  If this is left blank, it is assumed
;                  that 'internal name' == 'entry'.
; module: The name of the DLL (minus the .dll extension)
; entry: The name of the function you are importing.  Notice that
;        'internal name' and 'entry' don't have to be the same.
;
  ExportedFunction=basicdll.ExportedFunction
 

Want more? | for the more adventurous...
line.jpg (990 bytes)

Want to see what the code for a Windows program looks like?  Want to see what kind of stuff goes into that?  Then go download the sourcecode to basicdll.dll and dlluser.exe.  The code is well commented and you will prolly be able to learn alot about exactly what it takes to write a Windows program in straight C.  Head on over to the compilers sections to see a tutorial on how to compile this code using specific compilers.