Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Step by Step: Calling C++ DLLs from VC++ and VB - Part 4

0.00/5 (No votes)
2 Mar 2004 3  
This series of articles is a step-by-step guide to constructing C++ DLLs that include C++ functions and C++ classes, and then calling the DLL functions and classes from VC++ and VB programs.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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")    // no imports found

#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:

  1. Call via LIB class export - this calls the DLL via the standard exported LIB symbol for CDLL3.
  2. Call via class wrapper objptr - this calls the CreateDll3() wrapper function to get an object pointer, and then calls the class methods directly.
  3. Call via class wrapper function - this uses the class wrapper functions exported in the LIB file.
  4. 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:

  1. Load the library:
        CXLoadLibrary lib;
        if (!lib.LoadLibrary(_T("DLL3.dll")))
            return;
  2. Define a typedef for the function in the DLL - note the use of __stdcall:
        typedef void * (__stdcall *CreateFn)();
  3. Get the address of the function:
        CreateFn pfnCreate = 
            (CreateFn) GetProcAddress((HINSTANCE) lib, _T("CreateDll3"));
  4. 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

  • Initial public release.

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here