How to Use a DLL from Unmanaged C++When your application loads a DLL, Windows searches for it, just as it does when you run an executable. First the directory of the application loading the DLL is searched, and then the current directory, and then the system directory ( \Windows\System for Windows 95, 98, or XP, \Winnt\System or \Winnt\System32 for Windows NT or 2000), and then the Windows directory, and finally each directory specified in the path . If you plan to have a DLL shared by many applications, it's a good idea to put it in the System directory ( Winnt\System or Windows\System ) where it will be found easily. When one application installs an updated version of the DLL, all applications that use it will start to use the new one. If you prefer, you can copy the DLL to the same directory as the executable that uses it. You might end up with several copies of the DLL on your hard drive as a result. Some developers prefer to know that the DLL they are using will never be changed by anyone else, and consider a little wasted drive space a small price to pay for this reassurance. In this chapter, you copy the DLL to the same directory as the executable. The dumpbin UtilityVisual C++ comes with a utility called dumpbin that can show you what's going on inside a DLL. To run it, bring up a Visual Studio .NET 2003 command prompt, and then change directories to the directory containing the DLL. The utility takes several command-line options; the two most useful are /exports and /imports . To see all the methods exported from the legacy DLL, change to the appropriate directory, and then enter this command: dumpbin /exports legacy.dll You should see output like this: Microsoft (R) COFF/PE Dumper Version 7.10.3077 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file legacy.dll File Type: DLL Section contains the following exports for Legacy.dll 00000000 characteristics 3F2DDC3E time date stamp Mon Aug 04 00:08:30 2003 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00013479 Add 2 1 000134EC Log Summary 4000 .data 1000 .idata 5000 .rdata 3000 .reloc 25000 .text 12000 .textbss You can also uses the /imports option to see how many methods from other libraries this simple library uses. Try it; it's a real eye- opener ! Implicit Linking to a DLLWhen you build a DLL in Visual C++, a companion .lib file is generated for you as well. This file is called the import library ; it contains the definitions of all the functions in the DLL, but not the code. Working from unmanaged C++, you don't have to write any code that finds or loads the DLL. You just link with the import library as though it were a static library. This is called implicit linking of the DLL. It's the simplest approach to use. To test the DLL, start by creating an unmanaged console application (the details are in Chapter 2, "Creating Test Harnesses and Starter Applications") called UseLegacy . Copy legacy.h from the Legacy project folder to the UseLegacyDLL project folder, and copy both legacy.dll and legacy.lib from the Debug folder under Legacy to UseLegacyDLL. Edit the UseLegacyDLL.cpp file so that it reads as follows : // UseLegacyDLL.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "windows.h" #include "legacy.h" #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { cout << "1 + 2 is " << Add(1,2) << endl; SYSTEMTIME st; GetLocalTime(&st); Log("Testing", &st); cout << "log succeeded" << endl; return 0; } This code includes windows.h to get the declaration of SYSTEMTIME , and includes legacy.h to get the declarations of Add() and Log() . Because it writes to the screen with cout , it includes iostream and uses the std namespace. The main function exercises Add() , and then gets the local time and passes it to Log() . GetLocalTime() takes a pointer to a SYSTEMTIME structure, which it changes. It's defined in winbase.h, so you don't need any extra preparation to be able to use it. Use the property pages for the project to add legacy.lib to the linker dependencies. (Right-click the project in Solution Explorer, choose Properties, Expand the Linker section, select Input, and add legacy.lib to Additional Dependencies.) Build and run the project without debugging: You should see output like this: 1 + 2 is 3 log succeeded Press any key to continue Take a look in the root of your C drive for the file log.txt and open it in Notepad. You should see log messages (one for each time you ran the application) that look like this: 2003/8/4 - 11:14 Testing 2003/8/4 - 11:19 Testing 2003/8/4 - 11:35 Testing It's as simple as that to use an unmanaged DLL from an unmanaged application. You can apply these concepts to create a DLL for any of the kinds of unmanaged class libraries discussed in Chapter 5. Explicit Linking to a DLLAlthough implicit linking is the simplest approach, and works well for the sample in this chapter, it's not always the right approach. Explicit linking is needed when one of these situations apply:
Explicit linking works well in this situation because your application loads each DLL explicitly just before calling a function from it. The benefits are maximized when the functions in the DLL aren't always called each time the application runs. With implicit linking, all the DLLs are found and loaded just as the application starts running. That can make the application slow to start up, and increase the memory it uses. With explicit linking, a DLL that is never used is never loaded, and it's possible that fewer DLLs are in memory at any particular time. Most developers have no need to write the extra code to link the DLL explicitly. If you do, here are the steps:
You can try this with the DLL for this chapter by creating a Win32 console application called UseLegacyEx . Build the new project to create a Debug folder, and then copy legacy.dll from the Legacy Debug folder into the UseLegacyEx Debug folder. Do not copy the import library, legacy.lib, or the header file, legacy.h. Edit the UseLegacyEx.cpp file so that it looks like this: // UseLegacyEx.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "windows.h" #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { typedef bool (*FPLog)(char*, SYSTEMTIME*); typedef double (*FPAdd)(double, double); HINSTANCE hDLL; FPAdd fpadd; FPLog fplog; hDLL = LoadLibrary("legacy.dll"); fpadd = (FPAdd)GetProcAddress(hDLL, "Add"); cout << "1 + 2 is " << fpadd(1,2) << endl; SYSTEMTIME st; GetLocalTime(&st); fplog = (FPLog)GetProcAddress(hDLL, "Log"); fplog("Testing", &st); cout << " log succeeded" << endl; FreeLibrary(hDLL); return 0; } This code includes windows.h for the declaration of SYSTEMTIME , and iostream for cout . It also uses the std namespace. Notice that it does not include legacy.h or declare Add() and Log() .
Working with function pointers is a lot simpler if you use a typedef. That's especially true in this case, because GetProcAddress returns a generic pointer that must be cast to the correct kind of function pointer. You will need one typedef for each signature (parameter types and return type) in your DLL. Because Add() and Log() have different signatures, they each need a typedef. After setting up the typedefs and declaring a DLL handle and the function pointers, this code calls LoadLibary() to actually load the DLL, and saves the return value; GetProcAddress() and FreeLibrary() will need it. Each call to GetProcAddress() needs the DLL handle and the name of the function. Because name decoration was turned off in the Legacy DLL, you can pass undecorated names such as Add and Log . Once the function pointer is pointing to a function inside the DLL, you call it just as though the function pointer was a function, passing the parameters that are destined for Add() or Log() . Once all the functions have been called, FreeLibrary() unloads the DLL, reducing the memory used by the application. The extra work involved in linking a DLL explicitly is not enormous . Nonetheless, you should always start by writing code that links the DLL implicitly, and then test to see whether there are performance issues that explicit loading would improve. Only if it's necessary should you take on this extra work. |