Creating and Using DLLs with Visual C++ 6.0

by Matthew Mead

Contents

  1. Adding a new project
  2. Adding files to the project
  3. Exporting Functions
  4. Building the DLL
  5. Testing the DLL
  6. Peeking Inside the DLL

Getting Started

This tutorial assumes that you already have a Workspace containing the two Projects described below. A screen shot shows what the workspace window in Visual Studio should look like before getting started. The two projects contain the implementation files bits.c and uucode.c, respectively. They are stand-alone projects and contain a driver.c file that includes a main function.
You may notice that there are a couple of default folders missing from the two projects in the Workspace window. The folders that are missing are the Resource files folders. You can simply delete those, since they are not needed.

Add New Project

The first thing is to add a new project to the Workspace. This is done by right-clicking the mouse on the first line (workspace) in the Workspace window and choosing Add New Project to Workspace... from the context menu:
This brings up the New Project Dialog where you can select the type of project you want to create.
  1. Select the project type Win32 Dynamic-Link Library from the list of project types.
  2. In the Project name edit field, give the project a name. We will name our project CS220Utils.
  3. In the Location edit field, specify the directory to store this project. It should be the same as the project name. Make sure it's at the same level in the directory structure as the two existing projects. For this example, the two existing projects are located here: (your actual paths will vary)
    E:\Digipen\Courses\CS220\Code\DLLTutorial\Project1
    E:\Digipen\Courses\CS220\Code\DLLTutorial\Project2
    
    so the new project is placed here:
    E:\Digipen\Courses\CS220\Code\DLLTutorial\CS220Utils
    
  4. Make sure the Add to current workspace radio button is selected.
  5. Click the OK button to accept the changes.
The following dialog is presented:
  1. For this simple DLL, make sure that An empty DLL project is selected.
  2. Click the Finish button to accept the changes.
The New Project Information dialog is presented.
Click the OK button to dismiss the dialog box.

Your Workspace window should now look like this:

Adding Files to the Project

Now we need to add some files to the CS220Utils Project. The files already exist and are currently in Project1 and Project2. There are several ways that we can add files to a project. The technique shown below uses simple drag and drop within the Workspace window.
  1. Make sure that all 3 projects in the Workspace window are expand so you can see all of the source files.
  2. Select the file bits.c by clicking the left mouse on it.
  3. Select the file uucode.c by holding the Control key down while left-clicking the mouse. This will result in both files being selected simultaneously.
  4. Using the RIGHT mouse button, drag the two .c files to the CS220Utils Project and drop them on the Source Files folder.
  5. When you drop the files, a context menu will popup. Choose Copy to complete the copy.
  6. The Workspace window should now look like this:

Be aware that if you simply drag and drop the files from one project to another using the LEFT mouse button, you will actually perform a move operation, which means that you will be removing the files from the project where the files originated. For some tasks, this may be what you want to do, but for this task, we want the files to exist in both projects so we need to perform a copy operation.

Also note that, even though there appears to be multiple copies of each source file, there is only one physical file on the hard disk. This is desirable because we don't want to duplicate the code, we want to reuse it. This means that, for example, if you are working on the CS220Utils Project and you modify the bits.c file, you are actually modify the same file that is used by Project1 as well.

Alternative Method for Adding Files to the Project

If the file you wanted to add to the project wasn't part of any other project in the Workspace, you can use the "old-fashioned" way of adding files by using a browse dialog:

  1. Right-click the mouse on the Source files folder of the CS220Utils Project.
  2. Select the Add Files to Folder... option.
  3. Browse to the directory that contains the file(s) you want add and select it/them.

Exporting Functions

At this point, we could build the DLL and Visual Studio would create a valid DLL file from the two source files in the project. However, there is an additional step that we need to take in order to make the functions in the .c files accessible to clients. This step is called exporting the functions. By default, none of the functions in our .c files are accessible from outside of the DLL. The reason we need to export functions is because we may have many, many functions in our C modules, (which is typically the case), but only a few of them are meant to be called by the client. We will export only those functions that we want to make publicly accessible.

There are several ways to export functions, and we are going to use a simple technique that requires no changes to our source files. (In this tutorial our source files are bits.c and uucode.c.) To select functions to export, we need to create a Module Definition File. A module definition file is just a simple text file that has a file suffix of .DEF. The file contains a list of the functions we want to export.

For this project, create a text file named CS220Utils.def and add it to the source folder for the project.

To export all of our "public" functions from the DLL, make sure the contents of CS220Utils.def is as follows:
EXPORTS
  CharToBin
  ShortToBin
  IntToBin
  FloatToBin
  DoubleToBin
  FloatToIEEE754
  DoubleToIEEE754
  uuencode
  uudecode
If, for example, you only wanted clients to call your uuencode function, you would simply export only that function:
EXPORTS
  uuencode
That's all there is to exporting functions, at least for this simple DLL. There is a lot of other information that you can put inside the .def file, but this is the minimum. For more information on .def files, just search for "module definition files" on MSDN.

Building the DLL

At this point, we're ready to create the dynamic link library.
  1. Make sure that CS220Utils is the active project.
  2. Now build the project. (Choose Build CS220Utils.dll from the Build menu, press F7, or click on the Build icon.)
  3. If the build process was successful, the Output window should look like this:
  4. You can verify that you have, in fact, created a DLL called CS220Utils.dll by looking in the appropriate directory on your hard drive:
And that's all there is to it! Of course, that's only half of the battle. Now, we need to see how we can actually use this DLL in other projects, which is the reason we built it in the first place...

Testing the Dynamic Link Library in a Project

To test the DLL, we need to create an executable program.
  1. Create a Win32 Console Application and call it TestDLL. Make sure to add it to the current workspace.
  2. Create a new .c source file named main.c and add it to the TestDLL project.
  3. Place the code shown below into main.c and save it. Unlike a static library, the client has to do more work to use a DLL, but the flexibility of linking at runtime can be well worth it.
    
    // We actually need to include this because DLLs are Windows-specific
    #include <windows.h>  // HINSTANCE, LoadLibrary(), GetProcAddress(), FreeLibrary()
    #include <stdio.h>    // printf()
    
    void main(void)
    {
      const char *DLLName = "CS220Utils";      // Filename of the DLL that contains the function we need
      const char *FnName = "IntToBin";         // The function we want to call
      const unsigned char *(*MyIntToBin)(int); // A pointer to a function that takes an int and returns
                                               //   a pointer to a character (just like IntToBin!)
      HINSTANCE dll;  // a pointer
    
        // Load the DLL
      dll = LoadLibrary(DLLName);
    
        // If return is NULL, Windows couldn't find the DLL for us
      if (dll == NULL)
      {
        printf("Unable to load %s.\n", DLLName);
        return;
      }
    
        // Find the IntToBin function in the DLL. Since GetProcAddress returns
        // a pointer to an int, we need to cast it to the type of function
        // pointer we expect.
        //
        // This is also why we need to list the function in the EXPORTS section
        // of the module definition file. Failing to do so would certainly cause
        // GetProcAddress to return NULL.
      MyIntToBin = (const unsigned char *(*)(int)) GetProcAddress(dll, FnName);
    
        // If return is NULL, Windows can't find the function in the DLL
      if (MyIntToBin == NULL)
      {
        printf("Can't find function in DLL. Did you remember to export it?\n");
        FreeLibrary(dll); // Unload the DLL
        return;
      }
    
      //**********************************************************************
      // At this point, the DLL is loaded and the function has been located
      //**********************************************************************
    
        // Call the function and print out the bits
      printf("%s\n", MyIntToBin(127));
    
        // Unload the DLL
      FreeLibrary(dll);
    }
    
    
  4. Now, Build the program.
  5. The program should compile and link without warnings or errors.
  6. You should notice the client does not #include bits.h anywhere, yet we are able to compile our program. The reason for this is because nowhere are we calling the IntToBin function directly. We are indirectly getting a pointer to it, which we obtained by asking for it by name via the GetProcAddress function. Of course, if we aren't careful, the call to GetProcAddress might fail (return NULL) if we spelled the function wrong or loaded the wrong DLL. There are many things that can still go wrong (as you'll see now when we try to run the simple client).

    If we run the TestDLL console application, this is what is displayed:

    Apparently, the call to LoadLibrary failed:

      // Load the DLL
    dll = LoadLibrary(DLLName);
    
      // If return is NULL, Windows couldn't find the DLL for us
    if (dll == NULL)
    {
      printf("Unable to load %s.\n", DLLName);
      return;
    }
    
    The problem is that Windows can't find CS220Utils.DLL. When Windows searches for a DLL, it looks in specific directories on the computer. The first directory that it looks in is the directory where the executable that called LoadLibrary is running. In this case, the executable is TestDLL.exe, so Windows looks in:
    E:\Digipen\Courses\CS220\Code\DLLTutorial\TestDLL\Debug
    because that's where Visual Studio put TestDLL.exe when it created it. The second place Windows looks is in all of the directories that are specified in the PATH environment variable. A directory that is surely listed in the PATH variable is:
    C:\WINNT\System32
    which contains hundreds (or even thousands) of DLLs. I'm not going to get into the religious debate over where you should put your DLL, but it is almost always a Bad Idea (stemming from laziness, pure and simple) to put them into \System32. So, for this example, we are going to copy the DLL into the same directory where TestDLL.exe lives:
    Now when we run TestDLL.exe, we see what we expected to see the first time:

Peeking Inside the DLL

Given that there are thousands of DLLs on our systems, how do we know what's available for us to call? Well, the documentation for the DLL should tell us all we need to know. But, what if the DLL was created by a student at Digipen that hadn't yet discovered The Truth about code documentation? There are several tools that will allow you to "peek" inside of a DLL (or .exe) and see what it's made of.

The tool that I'm going to show is a free tool from Peter Vones called PE Viewer. Windows executables are in a format known as PE, which stands for Portable Executable (although I don't know how portable they are.) Search MSDN for "portable executable format" for more information.

If you run PEViewer and open CS220Utils.dll, you'll see the following displayed:

The section that we are interested in is the Exports section. This section lists all of the functions that are accessible to outside clients. As we would expect, all of the functions we listed in the .def file are listed here. The Imports section lists all of the DLLs that are used by our DLL. In this case, there's only one: kernel32.dll, which is used by almost all windows executables.

By the way, this kind of tool is invaluable when trying to determine on which DLLs your program is dependent. MS Visual Studio has a tool for listing dependent DLLs called Dependency Walker (depends.exe). You will probably find it on the Start menu (in MS Visual Studio Tools) in Windows. Or, if you don't have MS VS installed on a comptuer, Peter Vones also has a similar tool called Dependency Viewer which comes with the PE Viewer when you download it.

If you forget to export functions from your DLL (via a .def file or other means), you will not have an exports section in your DLL. GetProcAddress can't find a function unless it has been exported.


Thank you, drive through...