Loading a DLL

   

It was mentioned earlier that a DLL can be loaded statically or dynamically; it's important to understand the difference. A statically linked DLL is linked to the executable when the executable is built if the LIB is identified as part of the application's project. The DLL will then be loaded into memory when the executable is run (on start up). A dynamically linked DLL can be loaded and unloaded as needed by the application after start up, which can lessen the amount of resources the program needs to run. Another difference is that with static linking, a program will not run without the DLL, whereas a dynamically linked DLL doesn't have to be present. However, if the application makes a call to a function or attempts to utilize a resource contained within the DLL, but the DLL is not present, an error will occur. Now that we have an example DLL titled simple.dll , let's look at how we can load the DLL both statically and dynamically.

Linking DLLs Statically

To understand how to statically link a DLL, let's create a project that links the LIB file generated when simple.dll was built. The project that we will use as an example can be found on the CD-ROM in the SimpleDLL folder titled AppStatic.bpr . In this example, a Button and Edit control are placed on the main form. The Button, when triggered will call the simpleGetVersion() function of the DLL and fill in the Edit control with the version. The example for this code is provided in Listing 16.4.

Listing 16.4 Application Example That Uses the DLL Functions
 #include <vcl.h>  #pragma hdrstop  #include "AppStaticForm.h"  #include "simple.h"  //                                      - #pragma package(smart_init)  #pragma resource "*.dfm"  TForm1 *Form1;  //                                      - __fastcall TForm1::TForm1(TComponent* Owner)          : TForm(Owner)  {  }  //                                      - void __fastcall TForm1::ButtonGetVersionClick(TObject *Sender)  {    EditVersion->Text = AnsiString(simpleGetLibVersion());  }  //                                      - void __fastcall TForm1::ButtonConvertToMetersClick(TObject *Sender)  {       double meters =  feet_to_meters(EditFeet->Text.ToDouble());       EditMeters->Text = AnsiString(meters);  }  //                                      - void __fastcall TForm1::ButtonConvertToFeetClick(TObject *Sender)  {       double feet =  meters_to_feet(EditMeters->Text.ToDouble());       EditFeet->Text = AnsiString(feet);  } 

In this example, we need to include the header file of the DLL.

 #include "simple.h" 

This header file identifies the functions that we want to access from the DLL. The ButtonGetVersionClick() event-handler within our application code will call the DLL function simpleGetLibVersion() . Whereas, the ButtonCovertToMetersClick() event-handler calls feet_to_meters() , and ButtonConvertToFeetClick() calls meters_to_feet() .

If we compile and build the application without identifying the DLL library (LIB file) in the project we will receive the following error:

 [Linker Error] Unresolved external '_simpleGetLibVersion'                referenced from APPSTATICFORM.OBJ 

Therefore, in this example, we need to include the .lib file, as shown in Figure 16.4.

Figure 16.4. AppStatic project listing.

graphics/16fig04.gif

After we include the .lib file, we can then build and run our application. In this example, when the application launches, a message appears that is generated by the DLL, as illustrated in Figure 16.5.

Figure 16.5. DLL entry point Message Box displayed by simple.dll .

graphics/16fig05.gif

Following this message, the application GUI appears, and the user can press the buttons labeled Get DLL Version, Convert To Meters, and Convert To Feet, as illustrated in Figure 16.6. The event-handler for these buttons calls the exported DLL functions.

Figure 16.6. Screen shot of the AppStatic application.

graphics/16fig06.gif

That's all it takes to link and statically load a DLL. As long as you include the proper header file that identifies the DLL functions or classes to be used and include the .lib file in the project, everything else is the same as calling any other function. Be aware, however, that if the DLL is not physically present when the application launches an error occurs, as illustrated in Figure 16.7.

Figure 16.7. Unable to locate DLL error.

graphics/16fig07.gif

Loading DLLs Dynamically

The other way to use a DLL is to dynamically load it during execution. There are a few basic steps to dynamically link and use the functionality provided by a DLL:

  • Load the DLL and obtain a pointer to it.

  • Get a pointer to the function you want to call.

  • Call the function.

  • Free the DLL.

To load a DLL dynamically, we can use either the LoadLibrary() or LoadLibraryEx() provided by the Win32 API. To understand how to dynamically link a DLL, let's create a project that loads simple.dll using the LoadLibrary() call. The project that we will use as an example can be found on the CD-ROM in the SimpleDLL folder titled AppDynamic.bpr . A screen shot of the application is provided in Figure 16.8.

Figure 16.8. Screen shot of the AppDynamic application during execution.

graphics/16fig08.gif

First, to make this application work, we need to declare a variable within our program to receive an instance handle to the DLL. Within C++Builder, we can place the declaration of the DLL handle as a property within the class of our main form as follows :

 private:    // User declarations          HINSTANCE dllhandle; 

The following code in Listing 16.5 demonstrates how the LoadLibrary() call is made for loading simple.dll .

Listing 16.5 Using LoadLibrary()
 void __fastcall TForm1::ButtonLoadLibraryClick(TObject *Sender)  {      dllhandle = LoadLibrary("simplex.dll"); // keep track of the handle      EditDLLHandle->Text = AnsiString((int)dllhandle);      if (dllhandle)      {          ButtonUnloadLibrary->Enabled = true;          ButtonProcAddress->Enabled = true;      }      else      {          ShowMessage("Unable to load the DLL");      }  } 

LoadLibrary() attempts to load the DLL identified by its filename. In this example, we used "simple.dll" . You can also include a full path here. If just the name is provided, Windows will use the search paths to try and load it. If the DLL cannot be loaded, LoadLibrary() will return NULL .

After we load the DLL, the next step is to get a pointer to the function we want to call. There are two steps for importing functions from DLLs that are dynamically linked:

  • Create a new typedef using the exported functions prototype.

  • Cast each call to GetProcAddress() to the exported functions prototype.

We need to declare a variable to retrieve the process address of the desired function. However, preceding this variable declaration we need to identify its anticipated function format using a typedef such as this:

 typedef float (*SIMPLEGETLIBVERSION)();  SIMPLEGETLIBVERSION simpleGetLibVersion;  typedef double (*METERS_TO_FEET)(double);  METERS_TO_FEET meters_to_feet;  typedef double (*FEET_TO_METERS)(double);  FEET_TO_METERS feet_to_meters; 

Typically, this code is placed in the header file defined for our application, the top of the source file, or within an independent header file that we can include (link) in our code. The use of typedef identifies the format structure for the DLL function. The actual pointer to the DLL Function is then defined based on this type of definition.

In our code we want to cast the result from the GetProcAddress() call to the simpleGetLibVersion , meters_to_feet , and feet_to_meters function pointers. The code in Listing 16.6 demonstrates how the GetProcAddress() call can be used.

Listing 16.6 Using GetProcAddress()
 void __fastcall TForm1::ButtonProcAddressClick(TObject *Sender)  {    if (dllhandle)    {      simpleGetLibVersion  =          (SIMPLEGETLIBVERSION)GetProcAddress(dllhandle,                                "_simpleGetLibVersion");      if (simpleGetLibVersion)  ButtonGetVersion->Enabled = true;      meters_to_feet  =          (METERS_TO_FEET)GetProcAddress(dllhandle, "_meters_to_feet");      if (meters_to_feet)  ButtonConvertToMeters->Enabled = true;      feet_to_meters  =          (FEET_TO_METERS)GetProcAddress(dllhandle, "_feet_to_meters");      if (feet_to_meters)  ButtonConvertToFeet->Enabled = true;    }  } 

As shown in this example, the Windows API function GetProcAddress() returns a pointer to the exported functions provided by the DLL. In the first instance, the resultant is cast into the format identified by SIMPLEGETLIBVERSION type definition and assigned to the simpleGetVersion pointer. This is repeated for each of the other two functions provided by the DLL. For all these cases, an instance handle of a DLL and the title of the exported function we desire is required by the GetProcessAddress() call.

TIP

The preceding underscores for simpleGetLibVersion , meters_to_feet , and feet_to_meters in the GetProcAddress() functions are required because C++Builder adds them to the beginning of the functions exported in the DLL.


TIP

C++Builder by default adds an underscore to the beginning of the functions it exports. There is an option under Project, Options on the Advanced Compiler tab called Generate Underscores. If you uncheck this option within the project for your DLL, the function will export without the underscore , as shown in Figure 16.9.

Figure 16.9. The Project Options, Advanced Compiler tab.

graphics/16fig09.gif


CAUTION

Always be sure to check to make sure that the GetProcAddress() function succeeded before using the pointer. If the pointer is NULL , the GetProcAddress() call failed. Trying to use an invalid pointer will result in an access violation.


After you have a valid pointer, you can use it just like you would any other function. This is shown in Listing 16.7.

Listing 16.7 Making a Call to a DLL Function Based on a Pointer Retrieved Using GetProcAddress()
 void __fastcall TForm1::ButtonGetVersionClick(TObject *Sender)  {    if (dllhandle)    {      EditVersion->Text = AnsiString(simpleGetLibVersion());    }  }  //                                      - void __fastcall TForm1::ButtonConvertToMetersClick(TObject *Sender)  {       double meters =  feet_to_meters(EditFeet->Text.ToDouble());       EditMeters->Text = AnsiString(meters);  }  //                                      - void __fastcall TForm1::ButtonConvertToFeetClick(TObject *Sender)  {       double feet =  meters_to_feet(EditMeters->Text.ToDouble());       EditFeet->Text = AnsiString(feet);  } 

You might notice that this piece of code is exactly the same as the previous application that statically loaded simple.dll . The ButtonGetVersionClick() event-handler makes a call to the function simpleGetLibVersion() contained in simple.dll to retrieve and display the DLL version.

After we're done using this DLL, we want to free it. DLLs can be freed using the Win32 API function FreeLibrary() , which requires the parameter, the HINSTANCE Dll that we obtained earlier from the LoadLibrary() call. This is shown in Listing 16.8.

Listing 16.8 Using FreeLibrary()
 void __fastcall TForm1::ButtonUnloadLibraryClick(TObject *Sender)  {      if (dllhandle)      {          FreeLibrary(dllhandle);          ButtonUnloadLibrary->Enabled = false;          ButtonProcAddress->Enabled = false;          ButtonGetVersion->Enabled = false;          ButtonConvertToMeters->Enabled = false;          ButtonConvertToFeet->Enabled = false;          EditDLLHandle->Text = "";      } 

Those are the basic steps for loading a dynamic DLL.

Perhaps you might be thinking that the static DLL is much easier to use, which is true, but remember that the static DLL loads when your program starts and stays around until your program closes . Keep in mind if the static DLL is missing for some reason, your program won't run at all.


   
Top


C++ Builder Developers Guide
C++Builder 5 Developers Guide
ISBN: 0672319721
EAN: 2147483647
Year: 2002
Pages: 253

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net