A DLL is a windows specific library. The keyword Dynamic in the name implies that the compiled library exists outside of a program (.exe) and is loaded on runtime. The major advantages are that the library can be used by multiple applications without code duplication and can be updated without redeploying the entire application. Because of its independence, multiple programs can call the same DLL, although a separate instance is loaded for each at runtime – to share memory between applications, see Fundamentals::sharedMemory (coming soon).

An Important note: there are different types of DLL’s: Win32, .NET etc.; where each type has different variants of each: x86, x64, etc. ; and where within each DLL variant there are different calling methodologies: __stdcall, __cdecl, etc. While it is not so important to understand the particularities of each, it is very important that the calling application is configured to match the library. For simplicity, it is assumed that the user will follow the configuration laid out in the context of this work (Win32, x64, __cdecl). For details on how to configure dll projects in Visual Studio, see Application Development::Visual Studio Walk Through (coming soon).

  • The libraries you are planning to call inside the DLL will decide your architecture. For example, if you will be using a .NET library, then you must use a .NET architecture.
  • An x64 application cannot call a x86 DLL. Therefore, it is best to pick a single architecture version (x86 or x64) for both the application as well as the libraries until an advanced understanding of the differences between calling conventions and architecture limitations. It can be noted that there are ways to configure an application to call a x86 library when compiled as an x86 application and to call the x64 library version when the application is running as x64.
  • Native C++ (unmanaged) & Managed C++ (.net) are not the same and should not be thought of as the sample language. So much so that Microsoft has removed Managed C++ templates from Visual Studio to discourage its use! The industry preferred solution is to use unmanaged C++ where native code is required and C# where managed code is to be used.
  • Strive to program in the lowest level language that your architecture allows. For example, in this work we call Win32 Dll’s from a .net application by using Platform Invocation Services (PInvoke). The inverse is not easily possible; that is, you cannot call a .net DLL from a Win32 application!

The capabilities of the code inside a DLL depends on the compiler, however the interface is limited to a C programming construct. In other words, objects can be instantiated and used inside of the DLL but cannot be used as return types or taken as arguments.

Furthermore, it is encouraged to only use Blittable data types across a DLL interface; only using these data types, among many potential problems, avoids issues with the identical presentation in memory for both managed and unmanaged code (Int being defined as 32 bits in memory for example).

The following is a console application that demonstrates how a list of objects can be managed across a blittable interface. It uses a pre-allocated array of pointers and each object is created when the initNewBug() function is called – remember that it must be deleted if dynamic memory is used as shown. Each subsequent call across the interface uses the index of the object in the array as it’s reference.

//Class definition
class Bug {
	int numberOfLegs;
public:
	Bug() {
		numberOfLegs = 4;
	}
	void setNumLegs(int legs) {
		numberOfLegs = legs;
	}
	int getNumLegs() {
		return numberOfLegs;
	}
};

//----global variables--------
const int maxNumBugs = 10;
Bug* bugArray[maxNumBugs] = { NULL };

//----functions available via interface--------
int initNewBug(int numLegs) {
	int bugId = -1;
	for (int i = 0; i < maxNumBugs; i++) {
		if (bugArray[i] == NULL) {
			bugId = i;
			break;
		}
	}
	if (bugId > -1) {
		bugArray[bugId] = new Bug();
		bugArray[bugId]->setNumLegs(numLegs);
	}
	return bugId;
}
int getNumLegs(int id) {
	return bugArray[id]->getNumLegs();
}
void deleteBug(int id) {
	delete bugArray[id];
	bugArray[id] = NULL;
}

//----application using interface-----
int main()
{
	int ant = initNewBug(6);
	int spyder = initNewBug(8);
	std::cout << "index: " << ant << ", number of Legs: " <<  getNumLegs(ant) << std::endl;
	std::cout << "index: " << spyder << ", number of Legs: " << getNumLegs(spyder) << std::endl;
	deleteBug(ant);
	int bustedAnt = initNewBug(5);
	std::cout << "index: " << bustedAnt << ", number of Legs: " << getNumLegs(bustedAnt) << std::endl;
	deleteBug(bustedAnt);
	deleteBug(spyder);
}


A console application has an obvious entry point (the main() function). DLL’s have a more subtle entry point and for the most part is not even used. The following is the actual entry point for a Win32 DLL. This code usually resides in the projects source folder and typically has the name DLLmain.cpp.

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		//code here is called when the DLL is loaded by an application
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
		//code here is called before the application releases the DLL
        break;
    }
    return TRUE;
}


Notice that the DllMain function is one of many functions that might compose a library. This particular function is used by the Windows OS to manage events pertaining to your Library; if your only concern is to expose functions (make them available) to the calling application (the .exe) then this file and this function can be left alone.

  • Managed code is a Microsoft construct that enables many of the higher level features that are not inherent in the ISO/IEC JTC1/SC22/WG21 standards. These features include managed memory management, grammatical and syntactic extensions, keywords and attributes, ect… Most, if not all, of these features are developed to bring the C++ syntax and language to the .NET Framework.
  • In simpler terms: unmanaged code can be thought of as native c++ and can be compiled and targeted for any system, while managed C++ uses the .net framework and therefore can only be compiled using Visual Studio or its extensions and realistically targeted to run on windows. Note: it is possible to target managed code (.net applications) to mobile platforms however this is beyond the scope of this documentation.
  • Managed C++ supersedes Native C++: thus unmanaged code can be interpreted, and therefore used, in a managed application, however managed code cannot be used in a native c++ application.
  • Not all .net features are supported in languages such as LabVIEW and therefore unexpected behavior could be possible!


The button callback in Form1.cs

private void button1_Click(object sender, EventArgs e)
{
    if (Dll1.init(3, 5) == 8)
    {
        textBox1.Text = "success...";
    }
    else
        textBox1.Text = "no success...";
}


The class protype with P/Invoke definition in DLL1.cs

using System.Runtime.InteropServices;

//inside the class namespace and class prototype:
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
public static extern int init(int _screenHeight, int _screenWidth);


The function definition in DLL1.cpp

extern "C" __declspec(dllexport) int __cdecl init(int _screenHeight, int _screenWidth) {
	return _screenHeight + _screenWidth;
}