MFC .DLL TUTORIAL, PART 1

Environment: Visual C++

At one point in time, before COM, before ATL, programmers used ordinary .DLLs instead. You could do a lot with a .DLL. If you had several programs that used the same functions or other resources, you could save space by putting those resources in a .DLL. Putting code used by multiple programs in a single .DLL often saved maintenance time because the code was all in one place. Fixes and other modifications would only have to be done one time. If you had a program which needed to run different routines at different times, you could put those routines into .DLLs and have the application load the appropriate .DLL when it was needed. There were lots of good reasons to use .DLLs.

There are still a lot of good reasons to use .DLLs. They haven't gone away. Sure, whatever you can do with a .DLL, you can probably do with a COM object. Granted, there are a number of shortcomings to .DLLs, some of them serious, which is why we ended up with COM in the first place. Still, .DLLs remain a very useful tool. Compared to COM and ATL, they are much easier to make. Learning COM or ATL requires a serious investment of time and effort. Making a .DLL is relatively easy. Modifying one is easy too. If you know some C++ and MFC, you could be making .DLLs today.

This article will review the types of .DLLs you can make with MFC, including when to use each type and how to make them. In the next article there will be a discussion of the limitations of .DLLs (which led to the rise of COM and ATL), and how these can be partially avoided. In the third article, there will be more coding details and examples.

Different types of .DLLs

There are two kinds of .DLLs you can make using MFC: an MFC extension .DLL or a regular .DLL. Regular .DLLs in turn come in two varieties: dynamically linked or statically linked. Visual C++ also allows you to make a generic Win32 .DLL, but in this article I'm only going to discuss the MFC-based .DLL types.

MFC extension .DLLs

Every .DLL has some kind of interface. The interface is the set of the variables, pointers, functions or classes provided by the .DLL which you can access from the client program. They are the things that allow the client program to use the .DLL. An MFC extension .DLL can have a C++ style interface. That is, it can provide ("export") C++ functions and entire C++ classes to be used by the client application. The functions it exports can use C++ or MFC data types as parameters or as return values. When it exports a class, the client will be able to create objects of that class or derive new classes from it. Inside the .DLL, you can also use MFC and C++.

The MFC code library used by Visual C++ is stored in a .DLL. An MFC extension .DLL dynamically links to the MFC code library .DLL. The client application must also dynamically link to the MFC code library .DLL. As the years have gone by the MFC library has grown. As a result, there are a few different versions of the MFC code library .DLL out there. Both the client program and the extension .DLL must be built using the same version of MFC. Therefore, for an MFC extension .DLL to work, both the extension .DLL and the client program must dynamically link to the same MFC code library .DLL, and this .DLL must be available on the computer where the application is running.

Note: If you have an application which is statically linked to MFC, and you wish to modify it so that it can access functions from an extension .DLL, you can change the application to dynamically link to MFC. In Visual C++, select "Project | Settings" from the menu. On the "General" settings tab you can change your application to dynamically link to MFC.

MFC extension .DLLs are very small. You can build an extension .DLL which exports a few functions or small classes and has a size of 10-15 KB. Obviously, the size of your .DLL depends on how much code you store in it, but in general MFC extension .DLLs are relatively small and quick to load.

Regular .DLLs

The MFC extension .DLL only works with MFC client applications. If you need a .DLL that can be loaded and run by a wider range of Win32 programs, you should use a regular .DLL. The downside is that your .DLL and your client application cannot send each other pointers or references to MFC-derived classes and objects. If you export a function, it cannot use MFC data types in its parameters or return values. If you export a C++ class, it cannot be derived from MFC. You can still use MFC inside your .DLL, but not in your interface.

Your regular .DLL still needs to have access to the code in the MFC code library .DLL. You can dynamically link to this code or statically link. If you dynamically link, that means the MFC code your .DLL needs in order to function is not built into your .DLL. Your .DLL will get the code it needs from the MFC code library .DLL found on the client application's computer. If the right version of the MFC code library .DLL is not there, your .DLL won't run. Like the MFC extension .DLL, you get a small .DLL (because the .DLL doesn't include the MFC code), but you can only run if the client computer has the MFC code library .DLL.

If you statically link to the MFC code library, your .DLL will incorporate within itself all the MFC code it needs. Thus, it will be a larger .DLL, but it won't be dependent on the client computer having the proper MFC code library .DLL. If you can't rely on the host computer having the right version of MFC available, this is the way to go. If your application users are all within your own company, and you have control over what versions of the MFC .DLLs are lurking on their computers, or if your installation program also loads the right MFC .DLL, this might not be an issue.

Building a .DLL

You can make an MFC-based .DLL with the App Wizard. Select "File | New" from the menu. On the "Projects" tab, select "MFC AppWizard (.DLL)." Pick a name for your new project and click "OK." On the next screen, you will have the choice to create an MFC extension .DLL, a regular .DLL "using shared MFC .DLL" (i.e., a regular .DLL dynamically linked to MFC), or a regular .DLL statically linked to MFC. Pick the one you want and click "Finish."

App Wizard builds a .DLL which doesn't do anything. The new .DLL will compile, but since it doesn't export any classes or functions yet, it is still essentially useless. You now have two jobs: (1) add functionality to make your .DLL useful; and (2) modify your client application to use your .DLL.

Export a class

Once you're done with the App Wizard, you can add classes to your .DLL by adding the .cpp and .h files from another project, or you can create them from scratch within your current project. To export a class, you add "__declspec(dllexport)" to the class declaration so it looks like this:

  class __declspec(dllexport) CMyClass
  {
     //class declaration goes here
  };

If you are making an MFC extension .DLL, you can instead use the AFX_EXT_CLASS macro:

  class AFX_EXT_CLASS CMyClass
  {
     //class declaration goes here
  };

There are other ways to export a class, but this is the easiest. If your exported class requires a resource which is located in the .DLL, for example a class derived from CDialog, the process is more involved. I'll cover this subject in tutorial #3. Below I'll discuss what to do to the client application so that it can use your exported class.

Export variables, constants and objects

Instead of exporting a whole class, you can have your .DLL export a variable, constant or object. To export a variable or constant, you simply declare it like this:

__declspec(dllexport) int   MyInt;
__declspec(dllexport) extern const COLORREF MyColor = 
                                      RGB(50,50,50);

When you want to export a constant, you must use the "extern" specifier. Otherwise you will get a link error.

You can declare and export a class object in the exact same manner:

__declspec(dllexport) CRect MyRect(30, 30, 300, 300);

Note that you can only export a class object if the client application recognizes the class and has its header file. If you make a new class inside your .DLL, the client application won't recognize it without the header file.

When you export a variable or object, each client application which loads the .DLL will get its own copy. Thus, if two different applications are using the same .DLL, changes made by one application will not affect the other application.

It's important to remember that you can only export objects and variables which are of global scope within your .DLL. Local objects and variables cease to exist when they go out of scope. Thus, if your .DLL included the following, it wouldn't work.

MyFunction( )
{
     __declspec(dllexport) CSomeClass SomeObject;
     __declspec(dllexport) int SomeInt;
}

As soon as the object and variable go out of scope, they will cease to exist.

Export a function

Exporting functions is similar to exporting objects or variables. You simply tack "_declspec(dllexport)" onto the beginning of your function prototype:

__declspec(dllexport) int SomeFunction(int);

If you are making an MFC regular .DLL which will be used by a client application written in C, your function declaration should look like this:

extern "C" __declspec(dllexport) int SomeFunction(int);

and your function definition should look like this:

extern "C" __declspec(dllexport) int SomeFunction(int x)
{
     //do something 
}

If you are building a regular .DLL which is dynamically linked to the MFC code library .DLL, you must insert the AFX_MANAGE_STATE macro as the first line of any exported function. Thus, your function definition would look like this:

extern "C" __declspec(dllexport) int AddFive(int x)
{
     AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
     return x + 5;
}

It doesn't hurt to do this in every regular .DLL. If you you switch your .DLL to static linking, the macro will simply have no effect.

That's all there is to exporting functions. Remember, only an MFC extension .DLL can export functions with MFC data types in the parameters or return value.

Export a pointer

Exporting an uninitialized pointer is simple. You do it the same way you export a variable or object:

__declspec(dllexport) int* SomeInt;

You can also export an initialized object this way:

__declspec(dllexport) CSomeClass* SomePointer = 
                                     new CSomeClass;

Of course, if you declare and initialize your pointer you need to find a place to delete it.

In an extension .DLL, you will find a function called DllMain( ). This function gets called when the client program attaches your .DLL and again when it detaches. So here's one possible way to handle your pointers in an extension .DLL:

#include "SomeClass.h"

_declspec(dllexport) CSomeClass* SomePointer = new CSomeClass;

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
     if (dwReason == DLL_PROCESS_ATTACH)
     { 

     }
     else if (dwReason == DLL_PROCESS_DETACH)
     {
        delete SomePointer;
     }
}

A regular .DLL looks more like an ordinary MFC executable. It has an object derived from CWinApp to handle opening and closing your .DLL. You can use the class wizard to add an InitInstance( ) function and an ExitInstance( ) function.

int CMyDllApp::ExitInstance() 
{
     delete SomePointer;
     return CWinApp::ExitInstance();
}

Using the .DLL in a client application

A .DLL can't run on its own. It requires a client application to load it and use its interface. Making a client application that can do so is not difficult.

When you compile your .DLL, the compiler creates two important files: the .DLL file and the .lib file. Your client application needs both of these. You must copy them into the project folder of your client application. Note that the .DLL and .lib files that are created when you build in Debug are different that those built when you build in Release. When you are building your client application in Debug, you need the Debug versions of the .DLL and .lib files, and when you are building in Release you need the Release .DLL and .lib. The easiest way to handle this is to put the Debug .DLL and .lib files in your client application's Debug folder and the Release .DLL and .lib in the Release folder.

The next step is to go into your client project settings and tell the linker to look for your .lib file. You must tell the linker the name of your .lib file and where it can be found. To do this, open the project settings, go to the "Link" tab and enter your file name and path in the "Object/library modules" box. It should look something like this:

In addition to the .DLL and .lib files, your client application needs a header file for the imported classes, functions, objects and variables. When we were exporting, we added "__declspec(dllexport)" to our declarations. Now when we are importing, we will add "__declspec(dllimport)." So if we wanted to import the variable, object and function used in our previous examples, our header file would contain the following:

__declspec(dllimport) int SomeFunction(int);
__declspec(dllexport) CSomeClass SomeObject;
__declspec(dllexport) int SomeInt;

Remember, if you used the extern "C" specifier in the .DLL, you must also use it in the client application:

extern "C" __declspec(dllimport) int SomeFunction(int);

To make things more readable, we might write it like this instead:

#define DLLIMPORT __declspec(dllimport)

DLLIMPORT int SomeFunction(int);
DLLIMPORT CSomeClass SomeObject;
DLLIMPORT int SomeInt;

Now that you have declared your object, variable and function in a header file inside your client application, they are available for use.

To import an entire class, you must copy the entire .h header file into the client application. The .DLL and the client application will thus have identical header files for the exported class, except one will say "class __declspec(dllexport) CMyClass" and one will say "class __declspec(dllimport) CMyClass". If you are making an MFC extension .DLL, you could instead say "class AFX_EXT_CLASS CMyClass" in both places.

Once you are done building your client application and you're ready to turn it over to the actual users, you should give them your Release executable and the Release .DLL. You do not need to give the users the .lib file. The .DLL can go in the same directory as the executable, or it can go in the Windows System directory. As discussed above, you may also have to provide your users with the correct MFC code library .DLL. This .DLL was loaded onto your computer when you installed Visual C++. Your users, however, may not have it. It does not come standard with Windows.

A Word of Caution

This article should provide you with enough information to start building your own .DLLs. A word of caution is needed, however. As mentioned at the beginning of this article, there are several serious shortcomings to .DLLs. These shortcomings are the reason that we now have COM and ATL. There are two main problems. First, a .DLL built with one brand of compiler may not be compatible with a client application built with a different compiler. Second, when you modify the .DLL, you may have to recompile the client application, even though you aren't changing any code in the client application. You may still have to copy in a new .DLL and .lib file and recompile.

There are ways to avoid this problem under some circumstances. I'll discuss the problem in more detail in the next article.



Comments

  • good

    Posted by bagher on 07/01/2014 11:41pm

    goooooooooooooooooooooooooooooooooooooooooooooooooooooooooooood

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today makes data protection a must-have, as we live in a data-driven society -- the digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join Seagate Cloud …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds