Introduction
This series of articles discusses the following common situations when working with DLLs:
Part 1 |
Calling a DLL C++ function from a VC++ application |
Calling a DLL C++ class from a VC++ application |
Part 2 |
Calling a DLL C++ function from a VB application |
Part 3 |
Calling a DLL C++ class from a VB application |
Part 4 |
Loading a C++ DLL dynamically from a VC++ application |
Loading a C++ DLL dynamically from a VC++ application
VB does something automatically that takes some effort to accomplish in VC++ programs: dynamically loading DLLs. You can prove this for yourself very easily - just rename DLL3.dll in Part 3 and then try to run VB3.exe. It will start fine, but when you click the button, you will get an error message that the DLL cannot be found.
What is Dynamic Loading?
Normally, when you link to a DLL via a LIB file (for example, the MFC DLLs), the DLL is loaded when your application starts up. This kind of loading is referred to as implicit linking, because the system takes care of the DLL loading for you - all you have to do is link with the LIB file.
Dynamic loading (a.k.a. dynamic linking) means that your application loads a DLL just before you call a function in the DLL. For dynamic loading, you do not use a LIB file. Instead, what you do is call a pair of Win32 API functions (LoadLibrary
/GetProcAddress
) that load the DLL and then retrieve the address of a function in the DLL. Because you explicitly invoke these APIs, this kind of loading is also referred to as explicit linking. To summarize:
- implicit linking - DLL is loaded automatically when your app starts
- explicit linking - you write code to load DLL
This table will help sort out these terms:
Loading Method |
Synonyms |
static loading |
static linking |
implicit linking |
implicit loading |
automatic loading |
dynamic loading |
dynamic linking |
explicit linking |
explicit loading |
manual loading |
Why Use Dynamic Loading?
Before I get into the details of dynamically loading DLLs, let me first answer the question: When is it desirable to dynamically load a DLL? Here are the typical scenarios:
- You don't have a lib file to link with - this is a pretty lame reason, since if you worked at it you could generate a LIB file. On the whole, though, generating a LIB file is probably more work than just using
LoadLibrary
/GetProcAddress
to dynamically load a DLL.
- A DLL may not always be present - if you want to provide for some graceful program degradation, you must dynamically load any DLL that may or may not be present on the target machine (example: UXTHEME.DLL, which exists only on XP). If you used implicit linking, your application would never have the chance to degrade gracefully - the system simply would not allow your app to start, and would instead display some alarming message to your user.
- You need to support multiple feature sets - this is one of the historically valid reasons for using dynamic loading. If you have a product that supports many different features, and you want to load only those features that the customer has paid for, then what you do is package each feature set in its own DLL, and ship the DLL to the customer when he orders it. This is also a very convenient way to add new features (read: plug-ins) to your product, essentially making it open-ended.
- You need to support multiple platforms - this is also one of the historically valid reasons for using dynamic loading. You need to support multiple platforms (Win98, Win2000, WinXP) and each platform requires slightly different code for some reason. A simple solution is to segregate the code for each platform in its own DLL.
- You need to speed up the time it takes to load your application - this is another historical reason for using dynamic loading. You will start thinking about this when customers start complaining about how slow your app is to load. The idea is to identify what DLLs are necessary to display the core UI, and then dynamically load all the other DLLs that your app needs.
A Modern Alternative: Delay Loading
If you have been paying close attention, you will be wondering by now why I am using the term "historical". If you think there is a more modern way to implement dynamic loading other than the LoadLibrary
/GetProcAddress
technique, you are correct. In scenarios 4 and 5 above, you may be able to use a technique called delay loading to effectively implement dynamic loading in your application, with very little work.
OK, so what is delay loading? This is a new feature introduced with Visual C++� 6.0 that is implemented by a new /DELAYLOAD linker option. The interesting thing about delay loading is that it is not dependent on any operating system support - once you have linked your application with delay loading under VC 6 or later, it will work on any Windows OS. This is because the VC++ 6 linker actually generates code (no previous MS linker had ever generated code before). When you use delay loading, your VC++ application works just like a VB application - the DLL you have specified to be delay loaded won't be loaded until you make a call to one of its functions (personally, I think Microsoft missed a trick by not calling this "Just In Time Loading"). In practice, what you would do is test some condition, and then execute some code depending on the condition:
if (IsWinXP())
DisplaySpiffyXPThemedUI();
else
DisplayBoringUI();
Inside DisplaySpiffyXPThemedUI()
, you know it is safe to call the functions in UXTHEME.DLL, because you've already verified that you're running on XP. The first UXTHEME.DLL function that you call will cause UXTHEME.DLL to be loaded by the stub that the linker generated.
The neat thing is that all this happens transparently; you don't have to make any code changes at all to use delay loading (except, of course, you have to make sure that UXTHEME.DLL is present on the machine, before attempting to call its functions).
NOTE: There is one difference between delay loading and dynamic loading: with delay loading, you still need to link to a .LIB file, because the linker needs this information in order to create the delay-loading stub.
Using Delay Loading
To try out delay loading, I used the sample app EXE4 contained in the download for this article. After linking, I verified that the app was set to delay load DLL3 by using dumpbin /dependents exe4.exe:
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file exe4.exe
File Type: EXECUTABLE IMAGE
Image has the following dependencies:
MFC42.DLL
MSVCRT.dll
KERNEL32.dll
USER32.dll
GDI32.dll
ADVAPI32.dll
SHELL32.dll
VERSION.dll
Image has the following delay load dependencies:
DLL3.dll
Summary
2000 .data
2000 .rdata
1000 .rsrc
5000 .text
There are several ways to give linker commands in VC++ 6, but the way I prefer is to use #pragma
commands. This makes it less obscure, and so less likely to cause maintenance problems later. You can insert the #pragma
commands anywhere in the app's source code, even in the stdafx.h file. Here are the commands I use:
#pragma comment(linker, "/ignore:4199")
#pragma message("automatic link to delayimp.lib")
#pragma comment(lib, "delayimp.lib")
#pragma message("DLL3.dll will be delay loaded")
#pragma comment(linker, "/delayload:DLL3.dll")
TIP: To suppress the linker warning "no imports found from DLL3.dll", you can use the undocumented linker command /ignore:4199. If you are wondering whether delay loading is even happening, try renaming DLL3.dll to something else and then run EXE4.exe. With delay loading, EXE4 will start. Without delay loading, you will immediately get an error message.
WARNING: If you specify a DLL as delay loaded, you must ensure that it is present on the target machine, or there will be a very unfriendly application crash when your app calls into the DLL.
Using Dynamic Loading
Now, let's get back to dynamic loading. In the demo app EXE4.exe, I show how to call DLL3 using four techniques:
- Call via LIB class export - this calls the DLL via the standard exported LIB symbol for
CDLL3
.
- Call via class wrapper
objptr
- this calls the CreateDll3()
wrapper function to get an object pointer, and then calls the class methods directly.
- Call via class wrapper function - this uses the class wrapper functions exported in the LIB file.
- Call via dynamic linking (
LoadLibrary
) - this calls the class wrapper functions using LoadLibrary
/GetProcAddress
. The ::LoadLibrary()
API is encapsulated in the class CXLoadLibrary
, which is included in XLoadLibrary.cpp in the download for this article.
This last technique demonstrates how to load a DLL dynamically, and then use GetProcAddress()
to get the address of a function in the DLL. Here are the steps:
- Load the library:
CXLoadLibrary lib;
if (!lib.LoadLibrary(_T("DLL3.dll")))
return;
- Define a
typedef
for the function in the DLL - note the use of __stdcall
: typedef void * (__stdcall *CreateFn)();
- Get the address of the function:
CreateFn pfnCreate =
(CreateFn) GetProcAddress((HINSTANCE) lib, _T("CreateDll3"));
- Call the function via the
typedef
'd variable: void * objptr = pfnCreate();
Warning: The examples presented in this article have skimped on error checking. When using GetProcAddress()
, you must check the return address!
Macros For Dynamic Loading
There are quite a few steps involved when you use the LoadLibrary
/GetProcAddress
APIs to call several functions in a DLL. Jason De Arte has developed a set of LateLoad macros that simplify calling these APIs. They are available in his article LateLoad DLL Wrapper.
The download for this article includes DLL3Wrapper.h, which shows how to use the LateLoad macros to load the functions in DLL3.dll. The advantage of using these macros is that they simplify the code and provide consistent error checking in case the DLL or one of its functions cannot be found.
DLL3Wrapper.h contains this code:
LATELOAD_BEGIN_CLASS(CDLL3Wrapper, DLL3, FALSE, TRUE)
LATELOAD_FUNC_0(NULL,void *,STDAPICALLTYPE,CreateDll3)
LATELOAD_FUNC_1(0,int,STDAPICALLTYPE,GetCpuSpeedDll3,void *)
LATELOAD_FUNC_1_VOID(STDAPICALLTYPE,DestroyDll3,void *)
LATELOAD_END_CLASS()
and this code shows how the macros are used:
CDLL3Wrapper wrapper;
if (!wrapper.Is_CreateDll3())
{
AfxMessageBox(_T("can't find CreateDLL3"));
}
else
{
void * objptr = wrapper.CreateDll3();
int nSpeed = wrapper.GetCpuSpeedDll3(objptr);
CString s;
s.Format(_T("wrapper.GetCpuSpeedDll3() returned %d"), nSpeed);
m_Speed1.SetWindowText(s);
wrapper.DestroyDll3(objptr);
}
Demo
Here is the demo program that is included in the download:
Key Concepts
- Applications can link to DLLs by either static linking or dynamic linking.
- Using dynamic loading for DLLs gives you added flexibility for differing runtime environments.
- Using delay loading for DLLs is an easy way to speed up application loading.
Conclusion
In these articles, I have discussed how to call functions and classes in C++ DLLs, from both VC++ and VB programs. DLLs are a great way to encapsulate functionality and improve chances for reusability. When used correctly, DLLs will improve your productivity, allow you to enhance your applications with plug-ins, and utilize VB in a continuous rapid prototyping product life cycle.
Revision History
Version 1.0 - 2004 March 2
Usage
This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.