The Visual Studio 2008 C++ compiler has 2 important options which need to be set properly, especially when working with multiple projects and DLLs:
General\Use of MFC
and C/C++\Code Generation\Runtime Library
When you're working with a single project, the values of these 2 settings really don't matter. However, to be safe, best to set them to similar values.
When you're working with multiple projects, these values must be set appropriately. The default is "Use MFC in a Shared DLL" and "Multi-threaded DLL". What this means is that the implementation of the C-runtime and MFC are linked to MFC90.DLL and MSVCR90.DLL. These DLLs are normally installed in the Windows side-by-side (SxS) DLLs folder. Note the keyword "installed". Doing an xcopy installation without these DLLs being properly installed won't work (note the exception below).
If your application is meant to run without a full installation program (for example, an Autorun program), you should set these values to "Use MFC in a Static Library" and "Multi-threaded". What this means is that the implementation of the C-runtime and MFC are linked into your EXEs and DLLs. This will make your binaries larger, but you can run your binaries without doing a full install process.
There are some dangers to doing this.
New/Delete
You cannot allocate memory in one binary (DLL/EXE) and free the memory in another binary. Running the following application in the debugger will produce failures.
DLL.vcproj/ClassDLL.h:
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
class DLL_API CData
{
public:
int m_nData;
};
void DLL_API MyFreeFunction(CData *pPtr);
DLL.vcproj/ClassDLL.cpp:
#include "stdafx.h"
#include "ClassDll.h"
void MyFreeFunction(CData *pPtr)
{
delete pPtr;
}
EXE.vcproj/main.cpp:
#include "stdafx.h"
#include "../ClassDll/ClassDll.h"
void main()
{
CData *pPtr = new CData;
MyFreeFunction(pPtr);
}
If you execute this, you'll get an error in the delete call in MyFreeFunction(). The reason for the problem is that each binary has it's own implementation of new/delete/malloc/free/etc. Unfortunately, I don't know the nitty-gritty details of why this is an issue, so if there are any people who can give me more details, please let me know.
If you need to product this type of code, then you pretty much need to use shared DLLs.
Templates
Templates are strange creatures. Generally speaking, they are implemented in the modules where they are instantiated. So, if you have a template in a DLL, it's real binary code won't be produced until it's used in the EXE. If 2 binaries use the same template, each will have it's own binary code for the class.
Let's look at the following example:
DLL.vcproj/ClassDLL.h:
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
class DLL_API CData
{
public:
int m_nData;
};
void DLL_API MyFunction(CList<CData> &list);
DLL.vcproj/ClassDLL.cpp:
#include "stdafx.h"
#include "ClassDll.h"
void MyFunction(CList<CData> &list)
{
CData data;
data.m_nData = 5;
list.AddTail(data);
}
EXE.vcproj:main.cpp:
#include "stdafx.h"
#include "../ClassDll/ClassDll.h"
void main()
{
CList<CData> list;
MyFunction(list);
}
In this example, the list is being allocated and deallocated in the EXE, but an element is being added to the list in the DLL. If you run this, it will crash in the destructor for the list. Because the DLL has it's own implementation of the template, the allocation for the new list node occurred in the DLL, but it's trying to be freed in the EXE.
There is a fix for this.
If you add a destructor to CData and implemented it in the DLL. So files will become:
DLL.vcproj/ClassDLL.h:
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#define EXPIMP_TEMPLATE
#else
#define DLL_API __declspec(dllimport)
#define EXPIMP_TEMPLATE extern
#endif
class DLL_API CData
{
public:
int m_nData;
~CData();
// needed by CList now
BOOL operator == (const CData &other) const;
};
void DLL_API MyFunction(CList<CData> &list);
EXPIMP_TEMPLATE template class DLL_API CList<CData>;
DLL.vcproj/ClassDLL.cpp:
#include "stdafx.h"
#include "ClassDll.h"
CData::~CData()
{
}
BOOL CData::operator == (const CData &other) const
{
return (m_nData == other.m_nData);
}
void MyFunction(CList<CData> &list)
{
CData data;
data.m_nData = 5;
list.AddTail(data);
}
This will execute properly. The reason is because the EXPIMP_TEMPLATE ... line is defining a local implementation of the template in the DLL and exporting that implementation. The EXE then uses that implementation instead of compiling it's own.
Conclusion
When all is said and done, it's much easier to use the shared DLL settings. It does require an installation process, but it will avoid other development headaches.
Exception
I mentioned above that the MFC90.DLL and MSVCR90.DLL files must be installed into the Windows SxS subsystem. This is not 100% true. In the Visual Studio program directory is a redistribution folder which if copied to your applications folder, will allow it to run without installing the Microsoft binaries. For me, they are located at C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\... Just copy the x86 DLL as-is to your application folder.