Our community of experts have been thoroughly vetted for their expertise and industry experience. Experts with Gold status have received one of our highest-level Expert Awards, which recognize experts for their valuable contributions.
This tutorial is about how to put some of your C++ program's functionality into a standard DLL, and how to make working with the EXE and the DLL simple and seamless. We'll be using Microsoft Visual Studio 2008 and we will cut out the noise; that is, the end result will include a minimum of source code files so that you can tell what's important and what's not. We will create a VS Solution that contains two projects. You will be able to work with both or either easily, single-stepping with the debugger from the EXE code into the DLL code and back.
First a couple of definitions:
Standard DLL
This is the 'old' type of DLL (Dynamic Link Library) -- the foundational type of code module used in Windows and going back to OS/2 of 1985. Whenever you access a Win32 API -- or tap the OS for any common service -- you are calling a function in a DLL. Whenever you want to be able to reuse some code in multiple programs and/or split-off a logical chunk of functionality from your main C++ application, this is where you want to go.
ActiveX DLL
This is a DLL that exposes one or more COM interfaces and needs to be registered before use. Visual Basic programmers tend to think of these when they hear the word, "DLL," but we will not be addressing this type of DLL at all today.
Tutorial method:
Rather than walk you through the VS AppWizard, I'm going to show you how to do most of the setup work with a simple text editor, like NotePad. My reason is that when you use the AppWizard, it does a lot of "hidden" stuff for you. The end result is a tree of "What's this all about?"- type #include files and hidden settings that make the process seem more complicated than it really is.
Plan Ahead
We are envisioning a system in which we may eventually write multiple DLLs and multiple applications that access some or all of these DLLs. We want to be able to add DLLs and add EXEs as needed in a logical framework. We want to create a Visual Studios Solution that fits that image.
The DLL and the EXE will each have a folder for source code -- so each will be self-contained and easy to work with. Let's go ahead and create some folders. Use the Windows Explorer to create a "Master" folder and below it, a folder for the DLL and the EXE:
Next, let's make a plan for the DLL. We need to define its API (Application Programming Interface). What will it do? What functions will it expose? We'll make this simple. Our API will provide just two functions:
AddTwoNumbers ShowNumber
I personally like to use exported function names with a unique prefix -- so I can tell at a glance where the function is. We'll use MYDLL_ at the start of these function.
Create source code files for the DLL
We're going to create a .h header file that will be used by both the EXE and the DLL. In the MyProgMaster\MyDLL folder, create this file:
// file: MyProgMaster\MyDLL\Api_MYDLL.h// #include this in the DLL and in any EXE that uses it#if defined MYDLL_EXPORTS#define MYDLL_API __declspec(dllexport)#pragma message( "==============> compiling the MyDLL DLL (for export)" )#else#define MYDLL_API __declspec(dllimport)#endifextern "C" MYDLL_API int WINAPI MYDLL_AddTwoNumbers( int n1, int n2 );extern "C" MYDLL_API void WINAPI MYDLL_ShowNumber( int n );
A .DEF file can be used to identify the Exports. It's optional (the complier can handle this for you) but we'll use one here because it's easy to do and provides some direct visible control over what's going on. Create this file, also in the MyProgMaster\MyDLL folder.
And now, let's code up the CPP file and the .H header that will be #include'd in it and any other CPP file that will be part of the DLL:
// file: MyProgMaster\MyDLL\MyDLL.h (#include in all of the DLL's CPP files)#pragma once#define WIN32_LEAN_AND_MEAN #define WINVER 0x0500 // target Win 2000 or later#include <windows.h>
// file: MyProgMaster\MyDLL\MyDLL.cpp #include "MyDll.h"#include "Api_MyDll.h"#include <stdio.h> // used in printf, below//-------------- function needed for all DLLs. //-------------- We do nothing special, just return TRUEBOOL APIENTRY DllMain( HMODULE hModule,DWORD nReason,LPVOID lpReserved){ return TRUE;}//-------------- our two exposed API functionsMYDLL_API int WINAPI MYDLL_AddTwoNumbers( int n1, int n2 ){ int nRetVal= n1+n2; return( nRetVal );}MYDLL_API void WINAPI MYDLL_ShowNumber( int n ){ char szMsg[50]; sprintf_s( szMsg, sizeof(szMsg), "The number is: %d", n ); ::MessageBox( NULL, szMsg, "MY DLL", MB_OK);}
Now our directory structure looks like this: Create the Project
Now everything is set to have Visual Studio create the DLL project. Use the menu command:
File > New > Project From Existing Code...
Click Next, then enter the project location (the MyDLL directory) and set the name to MyDLL
Press Next, then set the Project type to Dynamically linked library (DLL) project Click Finish
Go ahead and compile the DLL. On my system it shows a single warning error but compiles without error. The warning is related to an option that the "Create from Files" wizard inserted. You can ignore it (or fix it by going to Properties / Configuration / C++ / Detect 64-bit Portability issues).
Create source code files for the EXE
Now we need to create the EXE program so we can exercise the DLL. As before, we'll start by creating the source code, then let VS create a project from it. Create these two files:
// file: MyProgMaster\MyApp\MyApp.h (#include in all of the EXE's CPP files)#pragma once#define WIN32_LEAN_AND_MEAN #define WINVER 0x0500 // target Win 2000 or later#include <windows.h>
Again, use the menu command:
File > New > Project From Existing Code...
Set the Directory to your MyProgMaster\MyApp folder
Set the name to MyApp
Click Next.
On the second wizard page, select Console Application Project
Click Finish.
The wizard has set the Solution name to be the same as the project. But for organizational purposes, we want this two-project Solution to have a different name. Right-click the Solution name and select Rename and set it to MyProgMaster
Now Add the DLL project that we created earlier. Right-click the MyProgMaster solution name and select
Add > Existing Project...
And browse to locate the MyDLL.vcproj file.
Click Open. Now all the players are in place:
Whenever we build the EXE, we want to make sure that the DLL also gets built -- if it has been changed recently. So...
Right-click MyApp
Select Project Dependencies
Put a check in the MyDLL checkbox
(Note: That also helps the linker find the DLL's .LIB file when it generates the EXE.)
Now Build the Solution (press F7). It builds without error. But when you run the EXE, you will get an ugly popup messages:
The application has failed to start because MyDll.dll was not found...
No Problem! DLLs are Dynamically Linked -- at runtime -- and the Operating System needs to be able to find them. Our DLL should be in the same directory as the EXE (or in the search path). We could just manually copy the DLL into the right directory, but that's no good -- the DLL will keep changing as we develop the project. So we just set Visual Studio to output the DLL into the EXE's directory:
Right-click MyDLL and select Properties
Set ...
Configuration > General > Output Directory
to...
..\MyApp\Debug
Run the Solution (VS will rebuild it, and put the DLL in the right place) and this time it runs without error.
Go ahead and try some source code changes. Modify the message in the MYDLL_ShowNumber function. Add some new functions to the API header file, the .DEF file, and the DLL CPP source file then try them out from the EXE. Debug the program and put a breakpoint in the MyApp's main function. When you single-step, you will step right into the DLL source code. If you put a breakpoint in the DLL, then when you run the EXE, the debugger will stop exactly as desired.
Conclusion
We now have a Solution that includes two projects -- a console application and a minimal Standard DLL. You can modify the DLL code and then when you debug the EXE, the new code will be in use. If you modify the Application program, the DLL will only be rebuilt if it has changed. Debugging is a breeze -- you can step seamlessly from DLL to EXE and place breakpoints as needed.
Some post-publishing notes:
In the Release version of the project, be sure to change the output directory for the DLL to set it into the Release folder for the EXE
I intentionally omitted the UNICODE-awareness stuff such as T_CHAR (rather than char) and TEXT("Hello World!") (rather than just "Hello World!"). I wanted to present the simplest possible scenario, and I've always found stuff like that to be unnecessarily distracting in a tutorial.
The MYDLL_EXPORTS and MYDLL_API macros constitute a standard combination in these EXE+DLL scenarios. The "Create From Files" Wizard automatically added /D MYDLL_EXPORTS to the DLL project settings. The result is that when #include'd in the DLL, MYDLL_API declares the function as an Export, but when #include'd in the EXE, it just declares an external function to be resolved by the linker.
In doing some experiments, I got stuck once when the compiler thought that MYDLL_EXPORTS was defined when it was working with the EXE code. If that happens, (you get some inscrutable errors on those lines of code) you can fix things up by changing the Properties for the EXE; it's in Configuration / C++ / Advanced / Undefine Preprocessors Definitions.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author, please click the Yes button near the:
Was this article helpful?
label that is just below and to the right of this text. Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Our community of experts have been thoroughly vetted for their expertise and industry experience. Experts with Gold status have received one of our highest-level Expert Awards, which recognize experts for their valuable contributions.
Comments (0)