Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Regular DLL Tutor For Beginners

0.00/5 (No votes)
15 Apr 2004 4  
Regular Win32 and MFC DLL tutorial for beginners.

Introduction

This article shows you how to create a Win32 and MFC DLL to dynamically link a Library to your application. Microsoft Foundation Class (MFC) library can be used to create simplified DLLs. The MFC supports two types of DLLs, regular and extension:

  • Regular DLL using shared MFC DLL
  • Regular DLL with MFC statically linked (Client doesn't have to be MFC based)
  • MFC Extension DLL (Using shared MFC DLL)

Win32 DLL (non-MFC Library based) in general, only supports regular DLLs. You should use Win32 DLLs when your DLL is not using the MFC Library, Win32 is substantially more efficient.

Extension DLLs are for developing re-useable binary code and it is for advance usage (such as ATL and COM). DLLs are very useful, especially if you want to create programs that are modular. You can go to the Microsoft MSDN web page if you want to know more on DLLs.

This tutorial is in five parts. It gives a step by step procedure for developing a Win32 DLL and a regular MFC DLL object. There are three client applications, two Win32 console applications (one for Load Time linkage and the other illustrates Run Time linkage), the third DLL client application uses the shared MFC Library.

Regular DLLs execute in the same memory space as the DLL client application, you don't have to worry about marshaling data and pointers across process boundaries.

Part One, The Win32 DLL Object

First, we are going to make the Win32 DLL core files for the project, W32DLL.xxx.

  1. Start Visual C++ Studio.
  2. Close any open workspace and all files.
  3. Select New from the file menu.
  4. Select Project: Win32 Dynamic-Link Library.
  5. Give the the project a fitting name, say, W32DLL, make sure the location is acceptable and click OK.
  6. Select the "A simple DLL project" radio button.
  7. Click Finish and OK.

Next, we are going to make the DLL declaration/header file: DLLCode.h. Select New from the file menu, then select "C/C++ Header File" and name the file DLLCode. Click OK.

Copy and paste the following code excerpt:

/*******************************************************
   File name: DLLCode.h

   This file contains all the DLL interfacing object 
   declarations, in this example:
   a class object, two global function object, and 
   a global integer variable. 
   Notice: we use the same header file for compiling the 
   .DLL and the .exe (application).
   This header file defines a macro which export the target 
   DLL objects if we are building
   a DLL, otherwise it import the DLL objects into an 
   application which uses the DLL. If
   we define DLLDIR_EX (a preprocessor identifier), 
   then the preprocessor define macro
   DLLDIR (a mnemonic for DLL import/export Direction) 
   becomes an export instruction,
   otherwise its an import instruction by default. 
************************************************************/ 
#ifdef DLLDIR_EX
   #define DLLDIR  __declspec(dllexport)   // export DLL information

#else
   #define DLLDIR  __declspec(dllimport)   // import DLL information

#endif 

// The extern "C" declaration allows mixed languages compactability,

// it prevents the C++ compiler from using decorated (modified)

// names for the functions 

extern "C" { 
       void DLLDIR DLLfun1(char*);
       int  DLLDIR DLLfun2(int);
}; 
extern int  DLLDIR DLLArg; 

class DLLDIR DLLclass
{
   public:
      DLLclass();          // Class Constructor

      ~DLLclass();         // Class destructor

      int Add(int, int);   // Class function Add

      int Sub(int, int);   // Class function Subtract

      int Arg;             // Warning: you should not 

               // import class variables

               // since the DLL object can be dynamically unloaded.

};

Save and close this header file. Now we are going to create the DLL implementation file, DLLCode.cpp.

Select New from the file menu, then select "C++ Source File" and name the file DLLCode. Then click OK.

Copy and paste the following code excerpt:

/*********************************************************
   File name: DLLCode.cpp 

   The header file, DLLCode.h, prototypes 
   all of the DLL interface objects 
**********************************************************/
#include "Stdafx.h"

#include "DLLCode.h"

#include <iostream> 


using namespace std; 

void DLLfun1(char* a)
{
   cout << a << endl;
}; 

int DLLfun2(int a) { return a<<1; }; 

int DLLArg = 100; 

DLLclass::DLLclass() {}; 
DLLclass::~DLLclass() {}; 

int DLLclass::Add(int a, int b)
{
   return a + b;
}; 

int DLLclass::Sub(int a, int b)
{
   return a - b;
};

We want to access the DLL, so we need to export it. When the DLL is built, it also builds something called an export library. The export library is similar to a regular library. It contains all of the information necessary to dynamically link to the DLL at runtime. When we export a function or a class, the function name and location is stored in the export library. The application uses the DLL links in the export library to access the DLL.

To create the DLL export library, select "setting..." from the Project menu. Select the C/C++ tab. Append, or insert, ",DLLDIR_EX" (without the quotation marks) to the Preprocessor Definition text box. Then click OK. This will prevent compiler assumptions and warnings.

Note, Visual C++ defines an export macro <projectname>_EXPORTS, in our case, W32DLL_EXPORTS. But we used DLLDIR_EX, a generic macro name.

Click the "!" button to compile, build, and run the W32DLL project.

Close the "Executable For Debug Session" dialog box, we ran the DLL prematurely.

Congratulation, you finished building the Win32 DLL and its export Library.

Part Two, DLL Client Application One

This is the simplest and most versatile DLL Client, without MFC. This flexibility is due to the DLL export library information.

Now, we are going to make a Win32 console application, project DLLClient1. In this application, the DLL is loaded at application startup, "Load Time" linkage.

  1. Close any open workspace and files, then select New from the File menu.
  2. Select Win32 Console Application.
  3. In the Project name box, give the project a fitting name, say, DLLClient1, then click Next.
  4. Select "An empty project", then click Finish and OK.

Next, we are going to make the C/C++ source file: DLLClient1. Select New from the File menu, then select "C/C++ Source File". In the File Name box, give it a fitting name, say, DLLClient1, then press the Enter key.

Copy and paste the following code excerpt:

/***********************************************************
   File name: DLLClient1.cpp
***********************************************************/ 
#include <iostream>

#include <conio.h>

#include <windows.h>

#include "DLLCode.h"

#pragma comment(lib,"W32DLL.lib") 

using namespace std; 

int main()
{
   int a, b, c;
   DLLclass classFromDLL; 
   classFromDLL.Arg = 6; 
   a = classFromDLL.Add(3, 2);
   b = classFromDLL.Sub(3, 2);
   c = classFromDLL.Arg;
  
   cout << "DLL class Add function return: " << a << endl;
   cout << "DLL class Sub function return: " << b << endl;
   cout << "DLL class Arg Variable return: " << c << endl; 
   getch();

   a = DLLArg;
   b = DLLfun2(30);

   DLLfun1("this is the string pass to function DLLfun1");

   cout << "\n\nDLL Variable DLLArg return: " << a << endl;
   cout << "DLL function DLLfun2 return: " << b << endl; 
   getch();
 
   return 0;
}

Save and close this C++ source file, then minimize the VC++ Studio window. We must get some common files from the W32DLL project. So, Copy, don't move, the following files to the DLLClient1 project directory:

  • W32DLL\Debug\W32DLL.DLL
  • W32DLL\Debug\W32DLL.lib
  • W32DLL\DLLCode.h

If you don't see the .DLL file in the Debug or Release folders, then select the View, or Tool (depending on the operating system) menu option, then select Folder Options. Next click on the View tab. Select option: Show all files. Then re-examine the Debug or Release folder.

Now maximize the VC++ Studio window, then click the "!" button to compile, build, and run the application.

If you have a DLL you use in many projects, then you can take advantage of the VC++ file architecture. Copy the DLL file into the system directory and the LIB file into the Visual C++ Lib directory. See Options on the Tools menu, and select the Directories tab, there are search directories for EXEs, SOURCE CODEs, and LIBs.

In this part, the DLL is loaded at application startup time. The Operating System searches the DLL in the following locations:

  • The Windows System directory
  • The Windows directory
  • The Application local path
  • The directories listed in the environment path variable

For simplicity, we just copy the common files to the target directory, DLLClient1.

Part Three, DLL Client Application Two

This DLL Client is for DLLs which do not have an export library, hints, we can't import the DLL header file. Note, we are restricted to function calls.

Now, we are going to make a Win32 console application, project DLLClient2.

In this application, the DLL is loaded during execution of the application, "Run Time" DLL linkage.

  1. Close any open workspace and files, then select New from the File menu.
  2. Select Win32 Console Application.
  3. In the Project Name box, give the project a fitting name, say, DLLClient2, then click Next.
  4. Select "An empty project", then click Finish and OK.

Next, we are going to make the C/C++ source file DLLClient2.

Select New from the File menu, then select "C/C++ Source File". In the File Name box, give it a fitting name, say, DLLClient2, then press the Enter key.

Copy and paste the following code excerpt:

/************************************************************
   File name: DLLClient2.cpp
************************************************************/ 
#include <conio.h>     // Header file containing getch() prototype

#include <iostream>

#include <windows.h> 


using namespace std; 

typedef void (*MYFUN1)(char*);  // pointer to: void function(char*)

typedef int  (*MYFUN2)(int);    // pointer to: int  function(int) 

int main()
{

  MYFUN1  pfun1;              
  MYFUN2  pfun2;              
  HMODULE hMod;        // handle to loaded library module

  BOOL    bRes;        // BOOL to check if DLL was successfully unloaded 


  // returns a handle to the DLL, otherwise NULL

  hMod = LoadLibrary("W32DLL.DLL");
 
  // returns the address of the DLL functions, otherwise NULL

  pfun1 = (MYFUN1) GetProcAddress(hMod, "DLLfun1");
  pfun2 = (MYFUN2) GetProcAddress(hMod, "DLLfun2");
 
  // (DLL function address) (function parameters)

  (pfun1)("this is the string pass to function DLLfun1");
 
  // (DLL function address) (function parameters)

  int a = (pfun2) (30);
 
  cout << "DLL function DLLfun2 return: " << a << endl;
  cout << "Press a key to exit" << endl;
  getch();
 
  ///////////////////////////////////////////////////////

  // This code will run if you compile the W32DLL project

  // with the W32DLL.def file to explicitly export DLLArg.

  int *i;
  i = (int*) GetProcAddress(hMod, "DLLArg");

  if (i)
  {
   cout << "Variable DLLArg is: " << *i << endl;
  };

  cout << "Press a key to exit" << endl;
  getch();

  // returns nonzero if sucussful 

  bRes = FreeLibrary(hMod); 
  return 0;
}
// ===========  Code snippet 4  ====================

Save and close this C++ source file, then minimize the VC++ studio window. Copy, don't move, the following file to the DLLClient2 project directory:

W32DLL\Debug\W32DLL.DLL

Now maximize the VC++ Studio window, then click the "!" button to compile, build, and run the application.

In this part, the DLL is loaded at application run time. The LoadLibrary function is used to load a DLL at run time. Being that we dynamically loaded the library, we should free this resource when we are finished with it.

Addendum

This subsection was added as a result to user Hing's comment.

The code in "Code snippet 4" was modified to add code for accessing the global variable DLLArg. Note, in the application you just ran, DLLCient2, function call GetProcAddress(hMod, "DLLArg") returned a NULL pointer. To add the global variable name to the DLL object, you must add a new text file to the W32DLL project. Go to New on the File menu. Select "Text File" on the File tag. Name the file: "W32DLL.def", the "def" file extension is important. Click OK.

Add the following text:

;File: W32DLL.def (explicitly export the global object names.)
LIBRARY W32DLL.dll
EXPORTS
   DLLfun1
   DLLfun2 
   DLLArg

Save and recompile the W32DLL project. Copy the new W32DLL.DLL file to the project DLLClient2 directory. Run the DLLClient2 application.

Function GetProcAddress should find variable "DLLArg".

Note: You CANNOT export a class object to the DLL. The library object contains that information, along with the header file.

Part Four, MFC DLL Object

This time, we are going to make the MFC DLL core files for the project, RMFCDLL.xxx.

  1. Start Visual C++ Studio.
  2. Close any open workspace and all files.
  3. Select New from the File menu.
  4. Select Project: MFC AppWizard(DLL).
  5. Give the the project a fitting name, say, RDLLMFC, make sure the location is acceptable and click OK.
  6. Select the "Regular DLL using shared MFC DLL" radio button.
  7. Click Finish and OK.

Next, we are going to make the DLL declaration header file: DLLCode.h. Select New from the File menu, then select "C/C++ Header File" and name the file DLLCode. Click OK.

Copy and paste the following code excerpt:

/******************************************************
   File name: DLLCode.h 

   This file contains MFC objects, hints, 
   you should use a MFC Client. 
   Notice: we use the same header file for compiling 
   the .DLL and the .exe (application).
   This header file defines a macro which export the
   target DLL objects if we are building
   a DLL, otherwise it import the DLL objects into an
   application which uses the DLL. If
   we define DLLDIR_EX (a preprocessor identifier), 
   then the preprocessor define macro
   DLLDIR (a mnemonic for: DLL import/export Direction)
   becomes an export instruction,
   otherwise its an import instruction by default. 
******************************************************/ 
#ifdef DLLDIR_EX
   #define DLLDIR  __declspec(dllexport)
#else
   #define DLLDIR  __declspec(dllimport)
#endif

extern "C" { 
       // We delete the function DLLfun1 (as defined 

       // in the W32DLL DLL) it writes to

       // a console, not a window, it isn't appropriate to 

       // call it from a MFC Client.

       int  DLLDIR DLLfun2(int);
       void DLLDIR DrawEllipse ( CRect, CDC* ); // a MFC function call 

}; 

extern int  DLLDIR DLLArg; 

class DLLDIR DLLclass
{
   public:
      DLLclass();          // Class Constructor

      ~DLLclass();         // Class destructor

      int Add(int, int);   // Class function Add

      int Sub(int, int);   // Class function Subtract

      int Arg;
};

Save and close this header file. Now we are going to create the DLLCode.cpp file. Select New from the File menu, then select "C++ Source File". Name the file: DLLCode. Click OK. Copy and paste the following code excerpt:

/************************************************************
   File name: DLLCode.cpp 

   The header file, DLLCode.h, prototypes all 
   of the DLL interface objects 
*************************************************************/
#include "StdAfx.h"

#include "DLLCode.h" 


int DLLfun2(int a) { return a<<1; }; 

int DLLArg = 100; 

DLLclass::DLLclass() {}; 
DLLclass::~DLLclass() {}; 

int DLLclass::Add(int a, int b)
{
   return a + b;
}; 

int DLLclass::Sub(int a, int b)
{
   return a - b;
}; 

void DrawEllipse ( CRect rect, CDC *pDC )
{ 
 CBrush brush;
 brush.CreateSolidBrush(RGB(0,0,255));
 pDC->SelectObject(&brush); 
 pDC->Ellipse(&rect);
};

Now, select "setting..." from the Project menu. Select the C/C++ tab. Append, or insert, ",DLLDIR_EX" (without the quotation marks) to the Preprocessor Definition text box. Then click OK.

Click the "!" button to compile, build, and run the RDLLMFC project. Close the "Executable For Debug Session" dialog box, we ran the DLL prematurely. Congratulations, you finished building the Win32 DLL and its export Library.

Part Five, DLL MFC Client Application

This client application is a MFC application. It provides the framework, a window, for the MFC CBrush object used in the DLL function DrawEllipse.

Now, we are going to make the MFC application project MFCAp.

  1. Close any open workspace and files, then select New from the File menu.
  2. Select Project: MFC AppWizard(exe).
  3. In the Project name box, give the project a fitting name, say, MFCAp, then click OK.
  4. Select the "Single document" radio button, then click Finish and OK.

Select "ClassWizard..." from the View menu. The Message Maps tab should be active. In the "Class name:" dropdown box, select "CMFCApView". In the "Message functions:" list box, double click the "OnDraw" function label.

You should see the View class OnDraw function code. Replace that stub code with the following code excerpt, use copy and paste:

///////////////////////////////////////////////

// CMFCApView drawing 


void CMFCApView::OnDraw(CDC* pDC)
{
   // DLL MFC Library function call

   CRect rect;
   rect.top=10;
   rect.left=10;
   rect.right=200;
   rect.bottom=200; 
   DrawEllipse(rect,pDC); 

   // DLL class object call

   int a, b, c;
   CString str;
   DLLclass classFromDLL; 
   classFromDLL.Arg = 6; 
   a = classFromDLL.Add(3, 2);
   b = classFromDLL.Sub(3, 2);
   c = classFromDLL.Arg;

   // Display data in window

   int y = 250, dy;
   TEXTMETRIC tm;
   pDC->GetTextMetrics(&tm);
   dy = tm.tmHeight + tm.tmExternalLeading; 
   str.Format("DLL class Add function return: %d", a);
   pDC->TextOut(20, y, str);
   y += dy;
   str.Format("DLL class Sub function return: %d", b);
   pDC->TextOut(20, y, str);
   y += dy;
   str.Format("DLL class Arg Variable return: %d", c);
   pDC->TextOut(20, y, str);
   y += dy; 
   a = DLLArg;
   b = DLLfun2(30); 
   str.Format("DLL class Arg Variable return: %d", a);
   pDC->TextOut(20, y, str);
   y += dy;
   str.Format("DLL function \"DLLfun2\" return: %d", b);
   pDC->TextOut(20, y, str);
}

At the top of the file MCFApView.cpp, and somewhere after the line containing: #include "stdafx.h", insert the following line:

#include "DLLCode.h"

Save and close the MFCApView.cpp file. Select "Setting..." from the Project menu. Select the "Link" tab. In the "Object/Library modules:" text box, enter: "RDLLMFC.lib", without the quotation marks. Then click OK.

Now, minimize the VC++ Studio window. Copy the following files to the MFCAp project directory:

  • RDLLMFC\Debug\RDLLMFC.DLL
  • RDLLMFC\Debug\RDLLMFC.lib
  • RDLLMFC\DLLCode.h

Now maximize the VC++ Studio window, then click the "!" button.

A note in closing, you shouldn't change the DLL interface, especially class objects which are exported therein, because the v-table and class size are fitted at compile time. If you change the DLL and other programs are using it, you should rename the new version; or alternatively, you should re-compile all programs based on that DLL with the new interface.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here