Introduction to VC ++ dynamic link library (DLL) Non-MFC DLL
4.1 A simple DLL
Section 2nd describes how to provide the add function interface using the static Link Library. Next we will look at how to use the dynamic link library to implement the add function with the same function.
6. Create a Win32 Dynamic-Link Library Project dllTest in VC ++ (Click here to download the attachment of this project ). Be sure not to select the MFC AppWizard (dll), because the MFC dynamic link library to be described in sections 5th and 6 will be created using the MFC AppWizard (dll.
Figure 6 create a non-MFC DLL
Add the lib. h and lib. cpp files to the created Project. The source code is as follows:
/** // * File name: lib. h */
# Ifndef LIB_H
# Define LIB_H
Extern "C" int _ declspec (dllexport) add (int x, int y );
# Endif
/** // * File name: lib. cpp */
# Include "lib. h"
Int add (int x, int y)
{
Return x + y;
} Similar to the call to the static Link Library in section 2nd, we also established an application project dllCall in the same workspace as the DLL project, which calls the function add in the DLL. Its source code is as follows:
# Include
# Include
Typedef int (* lpAddFun) (int, int); // macro-defined function pointer type
Int main (int argc, char * argv [])
{
HINSTANCE hDll; // DLL handle
LpAddFun addFun; // function pointer
HDll = LoadLibrary (".. DebugdllTest. dll ");
If (hDll! = NULL)
{
AddFun = (lpAddFun) GetProcAddress (hDll, "add ");
If (addFun! = NULL)
{
Int result = addFun (2, 3 );
Printf ("% d", result );
}
FreeLibrary (hDll );
}
Return 0;
}
Analyze the above Code, lib in the dllTest project. the cpp file is the same as the static Link Library version in section 2nd. The difference is that lib. h added the _ declspec (dllexport) statement before the function add statement. This statement declares that the function add is the DLL export function. There are two types of functions in the DLL:
(1) DLL export function, which can be called by a program;
(2) DLL internal functions can only be used in DLL programs, and applications cannot call them.
While the application calls this DLL and calls the static Link Library in section 2nd are quite different. Next we will analyze them one by one.
First, the statement typedef int (* lpAddFun) (int, int) defines a function pointer type that is the same as that accepted by the add function and returned values. Then, the instance addFun of lpAddFun is defined in the main function;
Secondly, a dll hinstance handle instance hDll is defined in function main. The DLL module is dynamically loaded through the Win32 Api function LoadLibrary and the DLL module handle is assigned to hDll;
Again, in the main function, use the Win32 Api function GetProcAddress to obtain the address of function add in the loaded DLL module and assign it to addFun. The function pointer addFun is used to call the add function in the DLL;
Finally, after the application project uses the DLL, the function main releases the loaded DLL module through the Win32 Api function FreeLibrary.
Through this simple example, we know the general concepts of DLL definition and calling:
(1) The DLL must declare the export function (or variable or class) in a specific way );
(2) The application project must call the DLL export function (or variable or class) in a specific way ).
Next we will elaborate on the "specific method.
4.2 declare the export Function
There are two ways to export the function declaration in DLL: one is to add _ declspec (dllexport) in the function declaration given in section 4.1; another way is to use the module definition (. def) file declaration ,. the def file provides the linker with information about export, attributes, and other aspects of the linked program.
The following code demonstrates how to declare function add as a DLL export function in the same. def file (the lib. def file must be added to the dllTest project ):
; Lib. def: Export DLL Functions
LIBRARY dllTest
EXPORTS
Add @ 1
The. def file rules are as follows:
(1) The LIBRARY statement describes the DLL corresponding to the. def file;
(2) Name of the function to be exported after the EXPORTS statement. You can add @ n after the export function name in the. def file to indicate that the sequence number of the function to be exported is n (this sequence number will play its role during function calling );
(3) The annotation in the def file is specified by the semicolon (;) at the beginning of each comment line, and the comment cannot share a line with the statement.
In this example, the lib. def file is used to generate a dynamic link library named "dllTest", export the add function, and specify the serial number of the add function as 1.
4.3 DLL call Method
In the example in section 4.1, we see the trinity "DLL load-DLL function address acquisition-DLL release" method provided by the "LoadLibrary-GetProcAddress-FreeLibrary" system Api, this call method is called DLL dynamic call.
Dynamic calling is characterized by API functions used by programmers to load and uninstall DLL files. programmers can determine when or not to load DLL files. explicit links determine which DLL file to load during runtime.
The static call method corresponds to the dynamic call method, and the "Dynamic and Static" method comes from the opposition and unity of the material world. The opposition and unification of "Dynamic and Static" have been verified countless times in technical fields, such as static IP and DHCP, Static Routing and dynamic routing. As we have known from the past, libraries are also divided into static libraries and dynamic library DLL. unexpectedly, the calling methods of libraries are also divided into static and dynamic. "Dynamic and Static" is everywhere. Zhou Yi has realized that there must be a static dynamic and static dynamic balance view, and "Yi. Department of Speech" said: "the dynamic and static, flexible and broken ". Philosophy means a universal truth, so we can often see the shadows of philosophy in the boring technical field.
Static calling is characterized by the completion of DLL loading by the compilation system and the uninstallation of DLL at the end of the application. When the application that calls a DLL ends, if other programs in the system use the DLL, Windows will subtract 1 from the DLL application record, it is not released until all programs that use the DLL end. The static call method is simple and practical, but it is not as flexible as the dynamic call method.
Next, let's take a look at the static call example (Click here to download the attachment of this project) and compile the generated by the dllTest project. lib and. the dll file is written to the path of the dllCall project, and dllCall executes the following code:
# Pragma comment (lib, "dllTest. lib ")
// The. lib file only contains information about the Function relocation in the corresponding DLL file.
Extern "C" _ declspec (dllimport) add (int x, int y );
Int main (int argc, char * argv [])
{
Int result = add (2, 3 );
Printf ("% d", result );
Return 0;
}
From the code above, we can see that the smooth execution of static call methods requires two actions:
(1) Tell the compiler the path and file name of the. lib file corresponding to the DLL, and # pragma comment (lib, "dllTest. lib") serves this purpose.
When a programmer creates a DLL file, the connector automatically generates a corresponding one. lib file, which contains the symbolic name and serial number of the DLL export function (excluding the actual code ). In an application, the. lib file is used as a DLL replacement file for compilation.
(2) declare the import function. The _ declspec (dllimport) in the extern "C" _ declspec (dllimport) add (int x, int y) Statement plays this role.
The static call method does not need to use system APIs to load or uninstall the DLL and obtain the address of the exported function in the DLL. This is because, when programmers compile an application through static links. function symbols matching the exported characters in the lib file will enter the generated EXE file ,. the file name of the corresponding DLL file contained in the lib file is also stored in the EXE file by the compiler. When a DLL file needs to be loaded while the application is running, Windows will find and load the DLL based on the information, and then use the symbolic name to implement dynamic links to the DLL function. In this way, EXE can directly call the DLL output function through the function name, just like calling other functions in the program.
4.4 DllMain Function
Windows requires an entry function when loading DLL, just as the console or DOS program requires the main function and WIN32 program requires the WinMain function. In the previous example, the DLL does not provide the DllMain function, and the application project can also reference the DLL. This is because Windows cannot find the DllMain function, the system will introduce a default DllMain function version from other runtime libraries without any operation. This does not mean that the DLL can discard the DllMain function.
According to the writing specifications, Windows must find and execute the DllMain function in the DLL as the basis for loading the DLL, which allows the DLL to be kept in the memory. This function is not an export function, but an internal function of the DLL. This means that you cannot directly reference the DllMain function in the application project. DllMain is automatically called.
Let's look at an example of the DllMain function (Click here to download the attachment of this project ).
Bool apientry DllMain (HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
Switch (ul_reason_for_call)
{
Case DLL_PROCESS_ATTACH:
Printf ("process attach of dll ");
Break;
Case DLL_THREAD_ATTACH:
Printf ("thread attach of dll ");
Break;
Case DLL_THREAD_DETACH:
Printf ("thread detach of dll ");
Break;
Case DLL_PROCESS_DETACH:
Printf ("process detach of dll ");
Break;
}
Return TRUE;
}
The DllMain function is called when the DLL is loaded and detached. When a single thread starts and stops, the DLLMain function is also called. ul_reason_for_call indicates the reason for the call. There are four causes, namely PROCESS_ATTACH, PROCESS_DETACH, THREAD_ATTACH, and THREAD_DETACH, which are listed in the switch statement.
Let's take a closer look at the DllMain function header bool apientry DllMain (HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved ).
APIENTRY is defined as _ stdcall, which means that this function is called in the standard Pascal method, that is, the WINAPI method;
Each DLL module in a process is identified by a globally unique 32-byte HINSTANCE handle and only valid within a specific process. The handle represents the starting address of the DLL module in the process virtual space. In Win32, the values of HINSTANCE and HMODULE are the same. You can replace these two types. This is the origin of the function parameter hModule.
Run the following code:
HDll = LoadLibrary (".. DebugdllTest. dll ");
If (hDll! = NULL)
{
AddFun = (lpAddFun) GetProcAddress (hDll, MAKEINTRESOURCE (1 ));
// MAKEINTRESOURCE directly uses the serial number in the exported file
If (addFun! = NULL)
{
Int result = addFun (2, 3 );
Printf ("call add in dll: % d", result );
}
FreeLibrary (hDll );
}
The output sequence is as follows:
Process attach of dll
Call add in dll: 5
Process detach of dll
This output sequence verifies the time when DllMain is called.
The GetProcAddress (hDll, MAKEINTRESOURCE (1) in the Code is worth noting. in the def file, the sequence number specified by the add function is used to access the add function. It is embodied in MAKEINTRESOURCE (1). MAKEINTRESOURCE is a macro that obtains the function name through the sequence number and is defined as (Excerpted from winuser. h ):
# Define MAKEINTRESOURCEA (I) (LPSTR) (DWORD) (WORD) (I )))
# Define MAKEINTRESOURCEW (I) (LPWSTR) (DWORD) (WORD) (I )))
# Ifdef UNICODE
# Define MAKEINTRESOURCE MAKEINTRESOURCEW
# Else
# Define MAKEINTRESOURCE MAKEINTRESOURCEA
4.5 _ stdcall conventions
If a DLL written in VC ++ is to be called by a program written in other languages, the function call method should be declared as _ stdcall. WINAPI adopts this method, the default call method of C/C ++ is _ cdecl. The _ stdcall method is different from the _ cdecl method for generating symbols for function names. If the C compilation method is used (the function must be declared as extern "C" in C ++), __stdcall indicates that the name of the output function must be underlined, the symbol "@" is followed by the number of bytes of the parameter, in the form of _ functionname @ number. The call Convention of _ cdecl is to only underline the name of the output function, in the form of _ functionname.
In Windows programming, macros of common function types are related to _ stdcall and _ cdecl (Excerpted from windef. h ):
# Define CALLBACK _ stdcall // This is the legendary CALLBACK function.
# Define WINAPI _ stdcall // This is the legendary WINAPI
# Define WINAPIV _ cdecl
# Define apientry winapi // The DllMain entry is here
# Define APIPRIVATE _ stdcall
# Define PASCAL _ stdcall
In lib. h, the add function should be declared as follows:
Int _ stdcall add (int x, int y );
In the Application project, the function pointer type should be defined:
Typedef int (_ stdcall * lpAddFun) (int, int );
In lib. h declares the function as _ stdcall, while typedef int (* lpAddFun) (int, int) is still used in the application project. An error will occur during runtime (because the type does not match, in the Application project, it is still the default _ cdecl call). The Dialog Box 7 is displayed.
Figure 7 running errors when the call conventions do not match
The section in Figure 8 actually shows the cause of the error, that is, "This is usually a result ...".
Click here to download the source code attachment of the _ stdcall call example project.
4.6 DLL export Variables
DLL-defined global variables can be accessed by the calling process; DLL can also access the global data of the calling process, let's take a look at the example of referencing the variable in DLL in the Application Project (Click here to download the attachment of this project ).
/** // * File name: lib. h */
# Ifndef LIB_H
# Define LIB_H
Extern int dllGlobalVar;
# Endif
/** // * File name: lib. cpp */
# Include "lib. h"
# Include
Int dllGlobalVar;
Bool apientry DllMain (HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
Switch (ul_reason_for_call)
{
Case DLL_PROCESS_ATTACH:
DllGlobalVar = 100; // when the dll is loaded, assign a global variable of 100
Break;
Case DLL_THREAD_ATTACH:
Case DLL_THREAD_DETACH:
Case DLL_PROCESS_DETACH:
Break;
}
Return TRUE;
}
; File name: lib. def
; Export variables in DLL
LIBRARY "dllTest"
EXPORTS
DllGlobalVar CONSTANT
; Or dllGlobalVar DATA
GetGlobalVar can be seen from lib. h and lib. cpp that the definition and usage of global variables in DLL are the same as that in general programming. To export a global variable, we need to add the following after the EXPORTS of the. def file:
Variable name CONSTANT // outdated method or
The variable name DATA // VC ++ prompts a new method to reference the global variables defined in DLL in the main function:
# Include
# Pragma comment (lib, "dllTest. lib ")
Extern int dllGlobalVar;
Int main (int argc, char * argv [])
{
Printf ("% d", * (int *) dllGlobalVar );
* (Int *) dllGlobalVar = 1;
Printf ("% d", * (int *) dllGlobalVar );
Return 0;
}
Note that the use of extern int dllGlobalVar declares that the imported global variable is not the DLL itself, but its address. The application must use the global variable in the DLL through forced pointer conversion. This can be seen from * (int *) dllGlobalVar. Therefore, when using this method to reference DLL global variables, do not assign values like this: dllGlobalVar = 1;
The result is that the content of the dllGlobalVar pointer changes, and the global variables in the DLL cannot be referenced in the program.
A better way to reference global variables in DLL in an application project is:
# Include
# Pragma comment (lib, "dllTest. lib ")
Extern int _ declspec (dllimport) dllGlobalVar; // use _ declspec (dllimport) to import
Int main (int argc, char * argv [])
{
Printf ("% d", dllGlobalVar );
DllGlobalVar = 1; // this can be used directly, without the need for forced pointer Conversion
Printf ("% d", dllGlobalVar );
Return 0;
}
The _ declspec (dllimport) method imports the global variables in the DLL instead of their addresses. I suggest using this method whenever possible.
4.7 DLL export class
Classes defined in DLL can be used in application projects.
In the following example, we define the point and circle classes in the DLL and reference them in the application project (Click here to download the attachment of this project ).
// File name: Declaration of the point. h and point classes
# Ifndef POINT_H
# Define POINT_H
# Ifdef DLL_FILE
Class _ declspec (dllexport) point // export class point
# Else
Class _ declspec (dllimport) point // import class point
# Endif
{
Public:
Float y;
Float x;
Point ();
Point (float x_coordinate, float y_coordinate );
};
# Endif
// File name: Implementation of point. cpp and point class
# Ifndef DLL_FILE
# Define DLL_FILE
# Endif
# Include "point. h"
// Default constructor of the Class point
Point: point ()
{
X = 0.0;
Y = 0.0;
}
// Point-like Constructor
Point: point (float x_coordinate, float y_coordinate)
{
X = x_coordinate;
Y = y_coordinate;
}
// File name: Statement of the circle. h and circle classes
# Ifndef CIRCLE_H
# Define CIRCLE_H
# Include "point. h"
# Ifdef DLL_FILE
Class _ declspec (dllexport) circle // export class circle
# Else
Class _ declspec (dllimport) circle // import class circle
# Endif
{
Public:
Void SetCentre (const point multicast rePoint );
Void SetRadius (float r );
Float GetGirth ();
Float GetArea ();
Circle ();
Private:
Float radius;
Point center;
};
# Endif
// File name: Implementation of the circle. cpp and circle classes
# Ifndef DLL_FILE
# Define DLL_FILE
# Endif
# Include "circle. h"
# Define PI 3.1415926
// Constructor of the circle class
Circle: circle ()
{
Center = point (0, 0 );
Radius = 0;
}
// Obtain the area of the circle
Float circle: GetArea ()
{
Return PI * radius;
}
// Obtain the circle perimeter
Float circle: GetGirth ()
{
Return 2 * PI * radius;
}
// Set the coordinates of the center
Void circle: SetCentre (const point duplicate rePoint)
{
Center = centrePoint;
}
// Set the circle radius
Void circle: SetRadius (float r)
{
Radius = r;
}
Class reference:
# Include ".. circle. h" // contains the class declaration header file
# Pragma comment (lib, "dllTest. lib ");
Int main (int argc, char * argv [])
{
Circle c;
Point p (2.0, 2.0 );
C. SetCentre (p );
C. SetRadius (1.0 );
Printf ("area: % f girth: % f", c. GetArea (), c. GetGirth ());
Return 0;
}
From the source code above, we can see that the macro DLL_FILE is defined in the DLL class implementation code, so the class declaration contained in the DLL implementation is actually:
Class _ declspec (dllexport) point // export class point
{
...
}
And
Class _ declspec (dllexport) circle // export class circle
{
...
}
DLL_FILE is not defined in the application project, so the class declaration introduced after point. h and circle. h is:
Class _ declspec (dllimport) point // import class point
{
...
}
And
Class _ declspec (dllimport) circle // import class circle
{
...
}
Yes, it is through the DLL
Class _ declspec (dllexport) class_name // export class circle
{
...
}
And
Class _ declspec (dllimport) class_name // import class
{
...
}
To export and import classes!
We usually use a macro in the class declaration header file to compile it into the class _ declspec (dllexport) class_name or class _ declspec (dllimport) class_name version, so that two header files are no longer required. This program uses:
# Ifdef DLL_FILE
Class _ declspec (dllexport) class_name // export class
# Else
Class _ declspec (dllimport) class_name // import class
# Endif
In fact, in the description of the mfc dll, you will see a simpler method than this, and here only to illustrate the problems of the _ declspec (dllexport) and _ declspec (dllimport) pairs.
It can be seen that almost everything in the DLL can be seen in the application project, including functions, variables, and classes. This is the powerful capability provided by the DLL. As long as the DLL releases these interfaces, the application will use it as it uses the program in this project!