MFC .DLL TUTORIAL, PART 2

Environment: Visual C++

As I discussed in the last article, .DLLs are a useful tool for any MFC programmer. They are subject to a number of important limitations, however, and anyone who is making .DLLs should be aware of these.

MFC Issues

This was discussed in the last article, but is worth mentioning again briefly. An MFC extension .DLL can only be used if the client application dynamically links to the same version of MFC, and the correct MFC code library .DLL is available on the client computer. A regular .DLL which dynamically links to MFC will only work if the correct MFC code library .DLL is available.

Compiler Incompatibility Issues

One of the biggest problems with C++ based .DLLs arises when a .DLL is built on one brand of compiler and called by an application built on another brand of compiler. Often it won't work without a great deal of effort.

ANSI sets the standards for the C and C++ languages. That is, it specifies the C and C++ functions and data types which should be supported by a compiler. It does not, however, provide a complete standard as to how these functions and data types should be implemented on a binary level. As a result, compiler vendors are free to implement language features in their own proprietary ways.

Most C / C++ programmers know that different compilers handle data types differently. One compiler will allocate 2 bytes for an int and another will allocate 4 bytes. One will use 4 byte doubles and another will use 8 bytes. An even bigger difference with C++ compilers arises from their implementation of function and operator overloading. The differences between compilers go far beyond this, however. The same C or C++ code may be compiled very differently by different compilers. These differences may keep your .DLL from running with someone else's application.

Of course, if you are building an MFC extension .DLL, this is not an issue for you. MFC extension .DLLs are made with a Microsoft compiler. As discussed in the previous article, they can only be used by applications that are dynamically linked to MFC. These applications are also made with a Microsoft compiler.

Compiler incompatibility problems can be fixed by inserting pragmas and other precompile instructions into your code, but this is hard to do and unreliable. There will always be the chance that someone else is using a compiler that's still incompatible.

Recompiling

Let's say you built a .DLL that exports a class called CMyClass. You provide a copy of the header file for CMyClass to be used by the client application. Suppose that a CMyClass object is 30 bytes in size.

Now let's suppose you modify the .DLL to change CMyClass. It still has the same public functions and member variables, but now CMyClass has an additional private member variable, an int. So now, when you create an object of type CMyClass, it's 34 bytes in size. You send this new .DLL to your users and tell them to replace the old .DLL. Now you have a problem. The client application is expecting a 30 byte object, but your new .DLL is creating a 34 byte object. The client application is going to get an error.

Here's a similar problem. Suppose that instead of exporting CMyClass your .DLL exports several functions which use CMyClass references or pointers in their parameters or return values. You have provided a CMyClass header file which now resides in the client application. Again, if you change the size of the CMyClass object without rebuilding the client application, you will have problems.

At this point, the only way to fix the problem is to replace the CMyClass header file in the client application and recompile it. Once recompiled, the client application will start looking for a 34 byte object.

This is a serious problem. One of the goals of having a .DLL is to be able to modify and replace the .DLL without modifying the client application. However, if your .DLL is exporting classes or class objects, this may not be possible. You may have to recompile the client application. If you don't have the source code for the client application, you simply can't use the new .DLL.

Solutions

If there were a perfect fix for these problems, we might not have COM. Here are a few suggestions:

MFC extension .DLLs don't have compiler incompatibility problems. They simply can't be used by applications built on non-Microsoft compilers. As for regular .DLLs, you can avoid many compiler problems by only exporting C-style functions and using the extern "C" specifier. Exporting C++ classes and overloaded C++ functions leaves you much more vulnerable to compiler incompatibility.

As for having to recompile your client application when you modify the .DLL, there are relatively easy ways to avoid the problem. I will describe two of them: (1) an interface class and (2) static functions used to create and destroy your exported class.

Using an Interface Class

The goal of an interface class is to separate the class you want to export and the interface to that class. The way to do this is to create a second class which will serve as the interface for the class you want to export. Then, even when the export class changes, you will not need to recompile the client application, because the interface class remains the same.

Here's an example of how that would work. Suppose you want to export CMyClass. CMyClass has two public functions, int FunctionA(int) and int FunctionB(int). If I simply export CMyClass, I'll have to recompile the client application every time I add a new variable. Instead, I'll create and export an interface class, CMyInterface. CMyInterface will have a pointer to a CMyClass object. Here's the header file for CMyInterface as it looks inside the .DLL:

#include "MyClass.h"

class __declspec(dllexport) CMyInterface
{
     //private pointer to CMyClass object
     CMyClass *m_pMyClass;
     
     CMyInterface( );
     ~CMyInterface( );
     
     public:
     int FunctionA(int);
     int FunctionB(int);
};

Inside the client application, the header file will look slightly different. The #include will be gone. After all, you can't include MyClass.h, because the client application doesn't have a copy of it. Instead, you will use a forward declaration of CMyClass. This will allow you to compile even without the CMyClass header file:

class __declspec(dllimport) CMyInterface
{
     //Forward declaration of CMyClass
     class CMyClass;
     
     CMyClass *m_pMyClass;
     
     CMyInterface( );
     ~CMyInterface( );
     
     public:
     int FunctionA(int);
     int FunctionB(int);
};

Inside the .DLL, you implement CMyInterface as follows:

CMyInterface::CMyInterface( )
{
     m_pMyClass = new CMyClass; 
}

CMyInterface::~CMyInterface( )
{
     delete m_pMyClass;
}

int CMyInterface::FunctionA( )
{
     return m_pMyClass->FunctionA( );
}

int CMyInterface::FunctionB( )
{
     return m_pMyClass->FunctionB( );
}

Thus, for every public function in CMyClass, CMyInterface will provide its own corresponding function. The client application has no contact with CMyClass. If it wants to call CMyClass::FunctionA, it instead calls CMyInterface::FunctionA. The interface class then uses its pointer to call CMyClass. With this arrangement, you're free to modify with CMyClass. It doesn't matter if the size of the CMyClass object changes. The size of the CMyInterface object will remain the same. If you add a private member variable to CMyClass, the size of CMyInterface will remain unchanged. If you add a public member variable to CMyClass, you can add "getter" and "setter" functions for the new variable in CMyInterface without fear. Adding new functions to CMyInterface will not cause recompile problems.

Creating a separate interface class avoids some compiler incompatibility problems and most recompile problems. As long as the interface class doesn't change, there should be no need to recompile. There are still two relatively minor problems with this solution. First, for every public function and member variable in CMyClass, you must create a corresponding function or variable in CMyInterface. In the example there are only two functions, so that's easy. If CMyClass had hundreds of functions and variables, this would be a more tedious, error-prone process. Second, you are increasing the amount of processing that must be done. The client application no longer calls CMyClass directly. Instead, it calls a CMyInterface function which calls CMyClass. If this is a function that will be called thousands of times by the client application, the extra processing time may begin to add up.

Static Functions

A different way to avoid having to recompile uses static functions to create and destroy the exported class. This solution was sent to me by Ran Wainstain in a comment to my previous article.

When you are creating a class which you intend to export, you add two public, static functions, CreateMe( ) and DestroyMe( ):

class __declspec(dllexport) CMyClass
{
    CMyClass( );
    ~CMyClass( );
     
     public:
     static CMyClass* CreateMe( );
     static void DestroyMe(CMyClass *ptr);
}

CreateMe( ) and DestroyMe( ) are implemented as follows:

CMyClass* CMyClass::CreateMe( )
{
     return new CMyClass;
}

void CMyClass::DestroyMe(CMyClass *ptr)
{
     delete ptr;
}

You export CMyClass as you would any other class. In the client application, you must be sure to use the CreateMe( ) and DestroyMe( ) functions. When you want to create a CMyClass object, you don't declare it in the usual fashion, i.e.:

CMyClass x;

Instead, you do this:

CMyClass *ptr = CMyClass::CreateMe( );

When you are done, you must remember to delete the object:

CMyClass::DeleteMe(ptr);

Using this technique, you can modify the size of CMyClass without having to recompile the client application.

Conclusion

This article is not a complete review of every issue concerning .DLLs, nor does it cover every possible solution. Good discussions of these issues can be found in Inside COM by Dale Rogerson and Essential COM by Don Box. For a more detailed understanding of the issue, that's where I would go. This article should at least serve to make you aware of the biggest issues and possible fixes and get you started on your way.



Comments

  • Prevent CMyClass declarations

    Posted by Legacy on 01/29/2004 12:00am

    Originally posted by: Sava Ionut

    The solution is simple. Just declare the constructor for CMyClass private so that anyone cannot use it without CMyInterface::CreateMe.
    Unfortunately you cannot declare a destructor private so you still can use : delete pCMyClassObj.

    Reply
  • Thanks

    Posted by Legacy on 12/21/2003 12:00am

    Originally posted by: Raul

    Thanks

    Reply
  • Help

    Posted by Legacy on 04/17/2003 12:00am

    Originally posted by: Nicola Dicosmo

    Hi,
    
    I have some dummy questions, can you help me?

    How can I export a class from a dll(no MFC dll)?

    Normally I have exported only function from my dlls, so I have used .def file,.lib file and the include file of my exported functions to make it.
    But if I use def file I have to use a particular sintax to export a class?
    It's enough to provide to client application the include file where I had declared my class?

    If I use your indications, could I use .def file or I have to use LoadLibrary() function to load my dll which export my class?
    Thank you in advance.
    Best Regards
    Nicola Dicosmo
    p.s.
    sorry for my English.

    Reply
  • Question about static functions for class export

    Posted by Legacy on 03/13/2003 12:00am

    Originally posted by: Christian

    Hi,

    first of all, this is a great article!

    well, i'm new to this topic and tried to write a simple dll that exports a class via static functions as described in the tutorial, but i have trouble importing them into the client app. (linker errors for CreateMe() and DestroyMe()) i guess that's because i'm not very experienced with Dlls, i'd really appreciate if someone could help me there, maybe writing a very simple DLL + client app and then posting it here or maybe mail it to me.

    thanks for any help
    -christian

    Reply
  • Very Good

    Posted by Legacy on 12/24/2002 12:00am

    Originally posted by: cs_brian

    I think, this article so cool.If I Have time,i will translate into Chinese for all programmer in China.

    Reply
  • A problem in the similar line...

    Posted by Legacy on 11/08/2002 12:00am

    Originally posted by: Satheesh Kumar

    Hello,
    
    This is my problem....
    The ActiveX control i wrote, in MFC, returns false from InitInstance() when user selects 'no' to a message box; i'm planning to replace this later with End User License Agreement.
    Whenever InitInstance returns FALSE, it will be called by NTDLL again. Which means, InitInstance() is called twice and the message box is displayed twice in the event user select 'no' at the first time.
    My test SDI/MDI/Dialog does not exhibit this behaviour; seems to me that this is a special case for ActiveX controls only.
    Any info on that?
    Thanks,
    -satheesh

    Reply
  • Using AciveX components in dll

    Posted by Legacy on 10/11/2002 12:00am

    Originally posted by: Manoj Phirke

    I tried to make a MFC dll with a user interface.

    I have sent handle of the parent to the dialog box in the dll and called the DoModal() function for the same. This works fine and dialog box gets displayed for check box, buttons etc.

    When I added any active x control (Microsoft rich edit control or Wang Image control). The dialog box stopped being displayed. It does not give any error message.

    What could be the problem?

    Thanks

    Reply
  • Using DLL's from Visual Basic

    Posted by Legacy on 09/17/2002 12:00am

    Originally posted by: Peter Chr. Hansen

    Hello there,
    
    

    I need to make a library og c functions that I can call from a Visual Basic program how do I do that ?

    I tried to make a MFC dll and added the following:

    extern "C" __declspec(dllexport) long OpenConnection( int );

    extern "C" __declspec(dllexport) long OpenConnection( int X )
    {
    //do something
    return (long)X;
    }


    And have made the following declare in my Vb app.

    Public Declare Function OpenConnection Lib "MyLib5" (ByVal X As Long) As Long

    the call:

    debug.print OpenConnection(56)

    I get the following error:

    Bad DLL calling convention

    If I don't use parm's then no problem..

    Reply
  • Nice work......... How do I have different dll name for debug, and release??

    Posted by Legacy on 09/04/2002 12:00am

    Originally posted by: S�ren Madsen

    I have read all your 3 articles with big interrests.

    I use Dll's a lot by myself, but I have one problem.

    Because that I uses Dll's a lot in my program, I have arranged the things in this way:

    Developer Studio copies the lib file, to C:\Program Files\Microsoft Visual Studio\VC98\Lib.

    Developer Studio copies the header file, to C:\Program Files\Microsoft Visual Studio\VC98\Include.

    Developer Studio copies the dll file, to C:\WINDOWS\SYSTEM32. In this way, all my upcomming programs can find the dll's on my computer, and they are always updated.

    But now we comes to the problem I have...... I want the name, on the dll in the release version, to be called FileName.dll and FileNamed.dll in the Debug Version. But I have terrible problems. I have tried to change the Output Filename in the linker settings, but then I got a LNK4070 error, which tells me, that the filename mentioned in .EXP file, is different than the filename gaved to the linker. It is a rather simple problem/qustion, but so far, I haven't found a soloution yet.

    Does anybody have a answer to me, or are you doing this debug/release handling in another way

    Reply
  • MFC .DLL TUTORIAL, PART 2

    Posted by Legacy on 06/26/2002 12:00am

    Originally posted by: Chanchal Kumar Sinha

    there is an issue using the dll. Actually, my requirement goes like this:
    
    

    One MDI application with a splitter window.

    The left view is there in the exe and it has also a user defined class

    class Splitter:public CSplitterWnd
    {
    };

    Now, the right views have to be added by the dll by using the SplitterWnd::CreateView() function by querying CRuntimeClass* information about the views that dll contains say

    RUNTIME_CLASS(CFormView1);

    Now this view has to be loaded once the view is stored in a Map varibale which is there with the exe. The view is also stored. This is the case when exe knows when the view has to be loaded.

    If suppose, the dll view contains a Button clicking which there should be another view loaded on the right pane of the splitter window. Now, since the map of views is there with the exe, dll can again give the info about the new view and then the exe can create a view for the dll.

    The problem is:

    There is a callback function with the exe.
    The exe registers this callback with the dll.
    the dll calls this function when it needs to load the view on the right pane of splitter window because the splitter window object is also there with the exe and the createview functionality is also there with the exe. But, the callback function which is already decalred static in the exe can't use the non-static member functions i.e. neither the splitterwindow object nor any other function so the view can't be created. there is some design issues here. Can you suggest me some way out..


    thanx.
    chanchal

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • "Security" is the number one issue holding business leaders back from the cloud. But does the reality match the perception? Keeping data close to home, on premises, makes business and IT leaders feel inherently more secure. But the truth is, cloud solutions can offer companies real, tangible security advantages. Before you assume that on-site is the only way to keep data safe, it's worth taking a comprehensive approach to evaluating risks. Doing so can lead to big benefits.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds