Native Windows Dynamic Link Libraries (DLLs)

This article briefly explains what a native Windows Dynamic Link Library (DLL) is, shows how to create a DLL using C++, how to consume it in C# and then explains how DLLs work. There are many types of DLLs but all of them are native DLLs, except the term native should only be used for DLLs that do not have a Type Library (COM) or CLI Metadata (.Net Class Library). If it is possible to create a reference to a DLL using the Visual Studio Add References window then the DLL should not be called a native DLL. When a C# program calls a native DLL it must use the DllImportAttribute class (DllImport for short), as described here. Use of DllImportAttribute/DllImport is also called Pinvoke.

A native Windows DLL is not a .Net Class Library. A .Net Class Library is a DLL with CLI Metadata and other additional things. A native DLL can only have unmanaged code; in other words, a compiled native DLL has native (x86) machine instructions but cannot have Common Intermediate Language (CIL) virtual machine instructions (CIL virtual machine instructions are also called Microsoft Intermediate Language (MSIL) instructions). There are many other articles explaining .Net Class Libraries using C# and other .Net languages. Unless a .Net Class Library is built using C++ it cannot have native (x86) machine instructions in it. To create a .Net Class Library using C++ see my article Managed C++ Wrapper For Unmanaged Code.

Understanding native DLLs is not a requirement for C# and Common Language Infrastructure programming. (CLI is often called .Net.) This article is intended for C# programmers unfamiliar with C++ that want to understand native DLLs and are brave enough to explore them. This article assumes you at least know how to create and use a C# Console Application project. I intend to explain in this article how to create a C++ DLL project you can use for educational purposes even if you are unfamiliar with C++.

What a Dynamic Link Library Is

A DLL file is separate from other executable files. The "Dynamic Link" portion of Dynamic Link Library refers to the fact that the functions and variables are linked dynamically. In contrast, a static library contains unmanaged code that is combined with other unmanaged code into a single executable. Programmers unfamiliar with C++ and unmanaged code might have difficulty understanding the concept of static libraries and static linking. When code that is statically linked is changed, every executable that uses that code must be updated. The majority of the Windows API exist in native Windows DLLs. Imagine how complicated it would be to update Windows if every Windows program (executable) had to be updated for every patch or fix for Windows.

Note that when a DLL project is built, there is a file with a "lib" extension created that is used by C/C++ projects but not by C#.

A DLL is a library of:

  • an optional entry-point function (DllMain) for the DLL
  • exported functions and data (variables)
  • internal functions and data

The DllMain entry-point function is called once when each of the following happens to the DLL:

  • a process loads the DLL
  • a process unloads the DLL
  • a thread loads the DLL
  • a thread unloads the DLL

This provides the DLL the opportuinty to initialize and uninitialize itself as needed. Note that there are limits to what can be or should be done in the DllMain function; Windows API functions cannot be called in the DllMain function except for those in Kernel32.dll. These limitations only apply to the DllMain function. The DllMain function must return TRUE unless it intends to indicate that the DLL has failed to load properly.

Creating the DLL

We will begin by creating the C++ DLL project. In Visual Studio start with:

"File" -> "New" -> "Project..."

You will then get the familiar "New Project" window as in:

NewProject.jpg

Then in the "New Project" window:

  1. In the left pane under "Installed Templates" (the default) expand "Visual C++".
  2. In the middle pane select "Win32 Project".
  3. Near the bottom give the project a name: I used "SampleNativeDLL".
  4. Change the Location if you need to or want to.
  5. Click "OK".

You will then get the "Welcome to the Win32 Application Wizard" window as in:

WizardWelcome.jpg

Just click "Next >" in the "Welcome to the Win32 Application Wizard" window.

The next window will be the "Application Settings" window as in:

ApplicationSettings.jpg

In it:

  1. For "Application type" select "DLL". The "Additional options" will change.
  2. For "Additional options" select "Export symbols". You will get very little sample code without this.
  3. Leave all the other settings as the default setting.
  4. Click "Finish".

The project will be generated and since we selected "Export symbols" the generated code will include additional sample code. The sample code will show how to define:

  •  a variable (nSampleNativeDLL)
  •  a function that is outside of a class (fnSampleNativeDLL)
  • a class (CSampleNativeDLL)

And how to export them for use by other applications.

A class cannot be exported from a DLL if the DLL is to be used by any language other than C++. So remove the CSampleNativeDLL class. To remove the CSampleNativeDLL class, in the "SampleNativeDLL.h" file remove:

// This class is exported from the SampleNativeDLL.dll
class SAMPLENATIVEDLL_API CSampleNativeDLL {
public:
CSampleNativeDLL(void);
// TODO: add your methods here.
};

In the "SampleNativeDLL.cpp" file remove:

// This is the constructor of a class that has been exported.
// see SampleNativeDLL.h for the class definition
CSampleNativeDLL::CSampleNativeDLL()
{
return;
}

We can now tell C++ that we want the exported function and variable to be accessible to the C language, because then they will be accessible to other languages too. To do that, near the top of the "SampleNativeDLL.cpp" file, after the two #include statements, add the following line:

extern "C" {

Then at the bottom of the file add a line with "}". In other words, the top of the "SampleNativeDLL.cpp" file should look like the following:

// SampleNativeDLL.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "SampleNativeDLL.h"
extern "C" {

Be sure to put the closing "}" at the bottom. Then do the same for the "SampleNativeDLL.h" file except since there are no #include statements in the SampleNativeDLL.h file, the extern "C" can go just below the comments at the top of the file. Then put the closing "}" at the bottom.

Creating the Console Application

Next we will create a C# application to use the DLL. We will keep it very simple so we will make it a Console applicaiton. I will assume you know how to do that. Be sure to change the language in the left (templates) pane back to C# from C++.

After creating the Console project, add the following two lines to the "Program" class:

[System.Runtime.InteropServices.DllImport("SampleNativeDLL.dll")]
public static extern int fnSampleNativeDLL();

Then add the following line to the "Main" nethod:

Console.WriteLine(fnSampleNativeDLL());

If you build and run the program now then during execution you will get the error that the DLL could not be loaded. That is because the Console Application does not know where the DLL is at. There are many possible solutions but for this article we will simply copy the DLL. There are many ways to do that but here is one. In Windows Explorer go to the directory where the DLL project is at. There will be one subdirectory called "Debug"; go to it and you will see the DLL; copy it. Then go to the directory where the Console Application project is at. Go to the "bin" subdirectory and then the "Debug" subdirectory of that. Paste the DLL there. Now when you execute the Console Application you should get "42" written to the console. That will happen because the Console Application is calling the fnSampleNativeDLL function in the DLL and that function is returning 42.

Explanation of the DLL Code

If you look at the generated DLL project then it will look as in the following:

ProjectFiles.jpg

The "ReadMe.txt" file is created by Visual Studio when the project is generated to describe the generated files. You can ignore the "External Dependencies". The remaining two folders are "Header Files" and "Source Files". For the purposes of this article we will ignore the files "targetver.h", "stdafx.h" and "stdafx.cpp".

The following is the contents of the "SampleNativeDLL.h" file after the modifications I described previously:

extern "C" {
// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the SAMPLENATIVEDLL_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// SAMPLENATIVEDLL_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef SAMPLENATIVEDLL_EXPORTS
#define SAMPLENATIVEDLL_API __declspec(dllexport)
#else
#define SAMPLENATIVEDLL_API __declspec(dllimport)
#endif
extern SAMPLENATIVEDLL_API int nSampleNativeDLL;
SAMPLENATIVEDLL_API int fnSampleNativeDLL(void);
}

Originally the primary need for header files is the result of the C language requiring that functions and variables be defined (in the file) before use. So a header file has the signatures (prototypes) of functions. Header files have what is required by any C/C++ file to use the functions defined in the header. The explanation of header files is not directly relevant to DLLs. What is relevant is that the "SampleNativeDLL.h" file has the following in it:

SAMPLENATIVEDLL_API int fnSampleNativeDLL(void);

That is the signature (prototype) of the fnSampleNativeDLL function. Note the use of the "SAMPLENATIVEDLL_API" macro. After the compiler processes the "SAMPLENATIVEDLL_API" macro the fnSampleNativeDLL function is defined as:

__declspec(dllexport) int fnSampleNativeDLL(void);

The purpose of  "__declspec(dllexport)" is to export the function for use by other programs. A "__declspec(dllimport)" would be used to import a function that is exported from a DLL. In C#, DllImportAttribute (DllImport for short) essentially serves the same purpose as "__declspec(dllimport)" in C/C++.

The following is the contents of the "SampleNativeDLL.cpp" file after the modifications I described previously:

// SampleNativeDLL.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "SampleNativeDLL.h"
extern "C" {
// This is an example of an exported variable
SAMPLENATIVEDLL_API int nSampleNativeDLL=0;
// This is an example of an exported function.
SAMPLENATIVEDLL_API int fnSampleNativeDLL(void)
{
return 42;
}
}

Note that that implements the "fnSampleNativeDLL" function. The function just returns the number "42".

The only file remainng to be described is "dllmain.cpp". The following is the contents of it:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD  ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

It has one function called DllMain that has previously been described.

The contents of the SampleNativeDLL.cpp and dllmain.cpp files could be combined into one file; they are made separate for our convenience.


Similar Articles