Usage DLL(dynamic link library) is widely used in Windows programming. DLL is actually part of the code of an executable file with the extension DLL. Any program can call DLL.

Advantage DLL is as follows:

  • Code reuse.
  • Share code between applications.
  • Code sharing.
  • Improved resource consumption in Windows.

Creating a DLL

On the menu File select New -> Other. In the dialog box on the tab New select DLL Wizard. A module will be created automatically - an empty template for the future DLL.

DLL Syntax

In order to build DLL, select Project -> Build Project_name .

Visibility of functions and procedures

Functions and procedures can be local and exported from DLL.

Local

Local functions and procedures can be used internally DLL. They are visible only inside the library and no program can call them from outside.

Exported

Exported functions and procedures can be used outside DLL. Other programs can call such functions and procedures.

The source code above uses the exported function. The function name follows reserved word Exports.

Loading DLLs

Delphi has two types of loading DLL:

Static loading

When you start the application it loads automatically. It remains in memory throughout the execution of the program. Very easy to use. Just add a word external after declaring a function or procedure.

Function SummaTotal(factor: integer): integer; register; external "Example.dll";

If DLL will not be found, the program will continue to run.

loaded into memory as needed. Its implementation is more complex because you have to load and unload it from memory yourself. Memory is used more sparingly, so the application runs faster. The programmer himself must ensure that everything works correctly. To do this you need:

  • Declare the type of the function or procedure being described.
  • Load the library into memory.
  • Get the address of a function or procedure in memory.
  • Call a function or procedure.
  • Unload the library from memory.

Declaration of a type that describes a function

type TSumaTotal = function(factor: integer): integer;

Loading the library

dll_instance:= LoadLibrary("Example_dll.dll");

We get a pointer to the function

@SummaTotal:= GetProcAddress(dll_instance, "SummaTotal");

Calling a function

Label1.Caption:= IntToStr(SummaTotal(5));

Unloading a library from memory

FreeLibrary(dll_instance);

Dynamic call DLL requires more work, but is easier to manage resources in memory. If you must use DLL within the program, then static loading is preferable. Don't forget to use a block try...except And try...finally to avoid errors during program execution.

Export strings

Created DLL using Delphi, can also be used in programs written in other programming languages. For this reason, we cannot use any data type. Types that exist in Delphi may not exist in other languages. It is advisable to use native data types from Linux or Windows. Our DLL can be used by another programmer who uses a different programming language.

Can be used lines And dynamic arrays V DLL, written in Delphi, but for this you need to connect the module ShareMem to section uses V DLL and the program that will use it. In addition, this ad must be the first in the section uses each project file.

Types string, as we know, C, C++ and other languages ​​do not exist, so it is advisable to use instead PChar.

Example DLL

library Example_dll; uses SysUtils, Classes; var (Declaring variables) k1: integer; k2:integer; (Declare a function) function SummaTotal(factor: integer): integer; register; begin factor:= factor * k1 + factor; factor:= factor * k2 + factor; result:= factor; end; (Exporting the function for further use by the program) exports SummaTotal; (Initializing variables) begin k1:= 2; k2:= 5; end.

Example of calling a function from a DLL

unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private ( Private declarations ) public ( Public declarations ) end; type TSummaTotal = function(factor: integer): integer; var Form1: TForm1; implementation ($R *.dfm) procedure TForm1.Button1Click(Sender: TObject); var dll_instance: Thandle; SummaTotal: TSummaTotal; begin dll_instance:= LoadLibrary("Example_dll.dll"); @SummaTotal:= GetProcAddress(dll_instance, "SummaTotal"); Label1.Caption:= IntToStr(SummaTotal(5)); FreeLibrary(dll_instance); end; end.

As you probably know, dynamic link libraries (DLLs) use C language conventions when declaring exported objects, while C++ uses a slightly different system for generating names when compiled, so you can't just export functions - methods of a C++ class and then use them in the client application code (hereinafter, the client means an application that uses the DLL). However, this can be done using interfaces that are available to both the DLL and the client application. This method is very powerful and at the same time elegant, because... the client sees only the abstract interface, and the actual class that implements all the functions can be anything. Microsoft's COM (Component Object Model) technology is built on a similar idea (plus additional functionality, of course). This article will show you how to use the "class" approach using a COM-like interface at an early (compile time) and late (while the program is running) binding.

If you have ever worked with DLLs, then you already know that DLLs have a special function DllMain(). This function is similar to WinMain, or main(), in that it is sort of the entry point into a DLL. The operating system automatically calls this function when the DLL is loaded and unloaded. Typically this function is not used for anything else.

There are two methods for connecting a DLL to a project - early (at program compilation) and late (during program execution) linking. The methods differ in the way the DLL is loaded and the way the functions implemented and exported from the DLL are called.

Early binding (during program compilation)

With this linking method, the operating system automatically loads the DLL when the program starts. However, it is required that the project being developed includes a .lib file (library file) corresponding to this DLL. This file defines all exported DLL objects. Declarations can contain regular C functions or classes. All the client needs to do is use this .lib file and include the DLL header file - and the OS will automatically load this DLL. As you can see, this method looks very easy to use, because... everything is transparent. However, you must have noticed that the client code needs to be recompiled whenever the DLL code is changed and, accordingly, a new .lib file is generated. Whether this is convenient for your application is up to you to decide. A DLL can declare the functions it wants to export in two methods. The standard method is to use .def files. This .def file is simply a listing of the functions exported from the DLL.

//================================================================ ============ // .def file LIBRARY myfirstdll.dll DESCRIPTION "My first DLL" EXPORTS MyFunction //=================== ====================================================== // DLL header that will be included in client code bool MyFunction(int parms); //================================================================ ============ // implementation of the function in DLL bool MyFunction(int parms) ( // do everything that is needed............ )

I think there is no need to say that in in this example Only one function, MyFunction, is exported. The second method of declaring exported objects is specific, but much more powerful: you can export not only functions, but also classes and variables. Let's look at the code snippet generated when VisualC++ AppWizard created the DLL. The comments included in the listing are enough to understand how it all works.

//================================================================ ============ // DLL header that must be included in the client code /* Next ifdef block - standard method creating a macro that makes exporting from a DLL easier. All files in this DLL are compiled with a specific key MYFIRSTDLL_EXPORTS. This key is not defined for any of the projects that use this DLL. Thus, any project that includes this file sees the MYFIRSTDLL_API functions as imported from the DLL, while the DLL itself sees these same functions as exported. */ #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec(dllexport) #else #define MYFIRSTDLL_API __declspec(dllimport) #endif // The class is exported from test2.dll class MYFIRSTDLL_API CMyFirstDll ( public: CMyFirstDll(void); // TODO: here you can add your own methods. ); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction(void);

During DLL compilation, the MYFIRSTDLL_EXPORTS key is defined, so the __declspec(dllexport) keyword is prefixed to exported object declarations. And when the client code is compiled, this key is undefined and the objects are prefixed with __declspec(dllimport) so that the client knows which objects are being imported from the DLL.

In both cases, all the client needs to do is add the myfirstdll.lib file to the project and include a header file that declares the objects being imported from the DLL, and then use those objects (functions, classes and variables) exactly as if they were defined and implemented locally in the project. Now let's look at another method of using DLLs, which is often more convenient and powerful.

Late binding (while the program is running)

When late binding is used, the DLL is not loaded automatically when the program starts, but directly in the code, where it is needed. No .lib files need to be used, so the client application does not require recompilation when the DLL changes. This linking is powerful precisely because YOU decide when and which DLL to load. For example, let's say you're writing a game that uses DirectX and OpenGL. You can simply include all the necessary code in the executable file, but then it will be simply impossible to understand anything. Or you can put the DirectX code in one DLL and the OpenGL code in another and statically connect them to the project. But now all the code is interdependent, so if you write a new DLL containing DirectX code, you will have to recompile the executable file as well. The only convenience is that you don't have to worry about loading (although whether this is a convenience is unknown if you load both DLLs, taking up memory, and only really need one of them). And finally, in my opinion, best idea is to let the executable decide which DLL to load at startup. For example, if the program determines that the system does not support OpenGL acceleration, then it is better to load a DLL with DirectX code, otherwise load OpenGL. Therefore, late linking saves memory and reduces the dependency between the DLL and the executable. However, in this case, a restriction is imposed on the exported objects - only C-style functions can be exported. Classes and variables cannot be loaded if the program uses late binding. Let's see how to get around this limitation using interfaces.

A DLL designed for late binding typically uses a .def file to define the objects it wants to export. If you don't want to use a .def file, you can simply use the __declspec(dllexport) prefix before the exported functions. Both methods do the same thing. The client loads the DLL by passing the name of the DLL file to the Win32 LoadLibrary() function. This function returns the HINSTANCE handle, which is used to work with the DLL and which is needed to unload the DLL from memory when it is no longer needed. After loading the DLL, the client can obtain a pointer to any function using the GetProcAddress() function, using the name of the required function as a parameter.

//================================================================ ============ // .def file LIBRARY myfirstdll.dll DESCRIPTION "My first DLL" EXPORTS MyFunction //=================== ====================================================== /* Implementation of the function in DLL */ bool MyFunction(int parms) ( //do something............ ) //======================= ===================================== //Client code /* The function declaration is really only necessary for in order to determine the parameters. Function declarations are usually contained in a header file supplied with the DLL. The extern C keyword in a function declaration tells the compiler to use the C variable naming conventions. */ extern "C" bool MyFunction(int parms); typedef bool (*MYFUNCTION)(int parms); MYFUNCTION pfnMyFunc=0; //pointer to MyFunction HINSTANCE hMyDll = ::LoadLibrary("myfirstdll.dll"); if(hMyDll != NULL) ( //Determine the address of the function pfnMyFunc= (MYFUNCTION)::GetProcAddress(hMyDll, "MyFunction"); //If unsuccessful, unload the DLL if(pfnMyFunc== 0) ( ::FreeLibrary(hMyDll) ; return; ) //Call the function bool result = pfnMyFunc(parms); //Unload the DLL if we no longer need it::FreeLibrary(hMyDll); )

As you can see, the code is pretty straight forward. Now let's see how working with "classes" can be implemented. As stated earlier, if late binding is used, there is no direct way to import classes from the DLL, so we need to implement the "functionality" of the class using an interface containing all public functions, excluding the constructor and destructor. The interface will be a regular C/C++ structure containing only virtual abstract member functions. The actual class in the DLL will inherit from this structure and will implement all the functions defined in the interface. Now, to access this class from a client application, all we need to do is export the C-style functions corresponding to the class instance and bind them to the interface we define so that the client can use them. To implement such a method, we need two more functions, one of which will create the interface, and the second will delete the interface after we have finished working with it. An example implementation of this idea is given below.

//================================================================ ============ // .def file LIBRARY myinterface.dll DESCRIPTION "implements the interface I_MyInterface EXPORTS GetMyInterface FreeMyInterface //===== ====================================================== // Header file used in Dll and client // which declares the interface // I_MyInterface.h struct I_MyInterface ( virtual bool Init(int parms)=0; virtual bool Release()=0; virtual void DoStuff() =0; ); /* Declarations of exported Dll functions and defining function pointer types for easy loading and working with functions. Note the extern "C" prefix, which tells the compiler that C-style functions are being used */ extern "C" ( HRESULT GetMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*GETINTERFACE)(I_MyInterface ** pInterface); HRESULT FreeMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*FREEINTERFACE)(I_MyInterface ** pInterface); ) //============ ================================================ // Implementation of the interface in Dll // MyInterface.h class CMyClass: public I_MyInterface ( public: bool Init(int parms); bool Release(); void DoStuff(); CMyClass(); ~CMyClass(); //any other members of the class............ private: //any members of the class............); //================================================================ ============ // Exported functions that create and destroy an interface // Dllmain.h HRESULT GetMyInterface(I_MyInterface ** pInterface) ( if(!*pInterface) ( *pInterface= new CMyClass; return S_OK; ) return E_FAIL; ) HRESULT FreeMyInterface(I_MyInterface ** pInterface) ( if(!*pInterface) return E_FAIL; delete *pInterface; *pInterface= 0; return S_OK; ) //========== ===================================================== // Client code //Interface declarations and function calls GETINTERFACE pfnInterface=0;//pointer to the GetMyInterface function I_MyInterface * pInterface =0;//pointer to the MyInterface structure HINSTANCE hMyDll = ::LoadLibrary("myinterface.dll"); if(hMyDll != NULL) ( //Determine the address of the function pfnInterface= (GETINTERFACE)::GetProcAddress(hMyDll, "GetMyInterface"); //Unload the DLL if the previous operation failed if(pfnInterface == 0) ( ::FreeLibrary (hMyDll); return; ) //Call the function HRESULT hr = pfnInterface(&pInterface); //Unload if unsuccessful if(FAILED(hr)) ( ::FreeLibrary(hMyDll); return; ) //The interface is loaded, you can call functions pInterface->Init(1); pInterface->DoStuff(); pInterface->Release(); //Release the interface FREEINTERFACE pfnFree = (FREEINTERFACE)::GetProcAddress(hMyDll,"FreeMyInterface"); if(pfnFree ! = 0) pfnFree(&hMyDll); //Unload DLL::FreeLibrary(hMyDll); )

This information is enough for you to feel all the convenience of using the interfaces. Happy programming!

There are three ways to load a DLL:

a) implicit;

c) postponed.

Let's look at implicit DLL loading. To build an application designed for implicit DLL loading, you must have:

Library include file with descriptions of used objects from DLL (function prototypes, class and type declarations). This file is used by the compiler.

LIB file with a list of imported identifiers. This file must be added to the project settings (to the list of libraries used by the linker).

The project is compiled in the usual way. Using object modules and a LIB file, as well as taking into account links to imported identifiers, the linker (linker, link editor) generates a boot EXE module. In this module, the linker also places an import section that lists the names of all required DLL modules. For each DLL, the import section specifies which function and variable names are referenced in the executable file's code. This information will be used by the operating system loader.

What happens during the execution phase of the client application? After launching the EXE module, the operating system loader performs the following operations:

1. Creates a virtual address space for a new process and projects an executable module onto it;

2. Analyzes the import section, identifying all the necessary DLL modules and also projecting them into the process address space.

A DLL can import functions and variables from another DLL. This means that it may have its own import section, for which you need to repeat the same steps. As a result, it can take quite a long time for the process to initialize.

Once the EXE module and all DLL modules are mapped to the process's address space, its primary thread is ready for execution, and the application starts running.

The disadvantages of implicit loading are the mandatory loading of the library, even if its functions are not called, and, accordingly, the mandatory requirement for the presence of the library when linking.

Explicitly loading a DLL eliminates the disadvantages noted above at the expense of some code complexity. The programmer himself has to take care of loading the DLL and connecting the exported functions. But explicit loading allows you to load DLLs as needed and allows the program to handle situations that arise in the absence of a DLL.

In the case of explicit loading, the process of working with the DLL occurs in three stages:

1. Load DLL using function LoadLibrary(or its extended analogue LoadLibraryEx). If the load is successful, the function returns an hLib descriptor of type HMODULE, which allows further access to this DLL.

2. Function calls GetProcAddress to obtain pointers to required functions or other objects. As the first parameter the function GetProcAddress receives the hLib descriptor, and as the second parameter the address of the string with the name of the imported function. The resulting pointer is then used by the client. For example, if this is a function pointer, then the desired function is called.

3. When a loaded dynamic library is no longer needed, it is recommended to free it by calling the function FreeLibrary. Freeing a library does not mean that the operating system will immediately remove it from memory. The unloading delay is provided for the case when the same DLL is needed again by some process after some time. But if there are problems with RAM, Windows first removes freed libraries from memory.

Let's look at lazy loading of a DLL. A delay-load DLL is an implicitly linked DLL that is not loaded until code accesses some identifier exported from it. Such DLLs can be useful in the following situations:

If an application uses multiple DLLs, initialization may take a long time for the loader to map all the DLLs into the process's address space. Lazy-loading DLLs solve this problem by distributing the loading of the DLL throughout application execution.

If an application is designed to work in different versions of the OS, then some of the functions may appear only in later versions of the OS and not be used in the current version. But if the program does not call a specific function, then it does not need the DLL, and it can safely continue to work. When referring to a non-existent

The function can be configured to issue an appropriate warning to the user.

To implement the lazy loading method, you must additionally add to the list of linker libraries not only the necessary import library MyLib.lib, but also system library import delayimp.lib. In addition, you need to add the / delayload:MyLib.dll flag to the linker options.

These settings cause the linker to perform the following operations:

Implement a special function in the EXE module
delayLoadHelper;

Remove MyLib.dll from the executable module import section so that the operating system loader does not attempt to implicitly load this library during the application boot phase;

Add to EXE file new section deferred import with a list of functions imported from MyLib.dll;

Convert function calls from DLL to calls
delayLoadhelper.

At the application execution stage, a function call from a DLL is implemented by calling delayLoadHelper. This function, using information from the deferred import section, calls first LoadLibrary and then GetprocAddress. Having received the address of a DLL function, delayLoadHelper makes sure that in the future this DLL function is called directly. Note that each function in the DLL is configured individually the first time it is called.

There are two ways to load a DLL: explicit linking and implicit linking.

With implicit linking, the functions of the loaded DLL are added to the import section of the calling file. When such a file is launched, the operating system loader analyzes the import section and includes all specified libraries. Due to its simplicity, this method is very popular; but simplicity is simplicity, and implicit layout has certain disadvantages and limitations:

all connected DLLs are always loaded, even if the program never accesses any of them during the entire operating session;

if at least one of the required DLLs is missing (or the DLL does not export at least one required function) - the loading of the executable file is interrupted by the message "Dynamic link library could not be found" (or something like that) - even if the absence of this DLL is not critical to execute the program. For example, text editor could work quite well in a minimal configuration - without a printing module, displaying tables, graphs, formulas and other minor components, but if these DLLs are loaded by implicit linking - like it or not, you will have to “pull” them along with you.

the DLL is searched in the following order: in the directory containing the calling file; in the current process directory; in the system directory %Windows%System%; in the main directory %Windows%; in the directories specified in the PATH variable. It is impossible to set a different search path (or rather, it is possible, but this will require making changes to system registry, and these changes will affect all processes running in the system - which is not good).

Explicit linking eliminates all these disadvantages - at the cost of some code complexity. The programmer himself will have to take care of loading the DLL and connecting the exported functions (while not forgetting about error control, otherwise at one point the system will freeze). But explicit linking allows you to load DLLs as needed and gives the programmer the opportunity to independently handle situations with lack of DLL. You can go further - do not explicitly set the DLL name in the program, but scan such and such a directory for the presence of dynamic libraries and connect all those found to the application. This is exactly how the plug-in support mechanism works in the popular FAR file manager (and not only in it).

From birth (or a little later) the operating room Windows system used DLLs (Dynamic Link Library), which contained implementations of the most commonly used functions. Windows' successors - NT and Windows 95, as well as OS/2 - also depend on DLLs for much of their functionality.

Let's look at a number of aspects of creating and using DLLs:

  • how to statically link DLLs;
  • how to dynamically load DLLs;
  • how to create DLLs;
  • how to create MFC DLL extensions.

Using DLLs

Almost impossible to create Windows application, which would not use DLLs. The DLL contains all the functions of the Win32 API and countless other functions of Win32 operating systems.

Generally speaking, DLLs are simply collections of functions collected into libraries. However, unlike their static cousins ​​(.lib files), DLLs are not linked directly to executables using a linker. The executable file contains only information about their location. At the time of program execution, the entire library is loaded. This allows different processes to share the same libraries in memory. This approach allows you to reduce the memory required for multiple applications that use many shared libraries, as well as control the size of EXE files.

However, if the library is used by only one application, it is better to make it a regular, static one. Of course, if the functions included in it will be used only in one program, you can simply insert the corresponding source text file into it.

Most often, a project is connected to a DLL statically, or implicitly, at link time. The loading of DLLs during program execution is controlled by the operating system. However, DLLs can be loaded either explicitly or dynamically while the application is running.

Import Libraries

When statically linking a DLL, the name of the .lib file is determined among other parameters of the link editor in command line or from the Link tab of the Project Settings dialog box in Developer Studio. However, the .lib file used when implicitly connecting a DLL,- this is not an ordinary static library. Such .lib files are called import libraries(import libraries). They do not contain the library code itself, but only links to all functions exported from the DLL file in which everything is stored. As a result, import libraries tend to be smaller in size than DLL files. We'll return to how to create them later. Now let's look at other issues related to the implicit inclusion of dynamic libraries.

Interface negotiation

When using your own or third-party libraries, you will have to pay attention to matching the function call with its prototype.

If the world were perfect, then programmers would not have to worry about matching function interfaces when connecting libraries - they would all be the same. However, the world is far from perfect, and many large programs written using various libraries without C++.

By default, Visual C++ conforms function interfaces according to C++ rules. This means that parameters are pushed onto the stack from right to left, and the caller is responsible for removing them from the stack when exiting the function and expanding its name. Name mangling allows the linker to distinguish between overloaded functions, i.e. functions with the same names, but different lists arguments. However, the old C library does not have expanded-named functions.

Although all other rules for calling a function in C are identical to the rules for calling a function in C++, function names are not expanded in C libraries. They are only preceded by an underscore (_).

If you need to connect a C library to a C++ application, all functions from this library will have to be declared as external in C format:

Extern "C" int MyOldCFunction(int myParam);

Declarations of library functions are usually placed in the library's header file, although the headers of most C libraries are not designed for use in C++ projects. In this case, you must create a copy of the header file and include the extern "C" modifier in the declaration of all library functions used. The extern "C" modifier can also be applied to an entire block, to which the old C header file is connected using the #tinclude directive. Thus, instead of modifying each function separately, you can get by with just three lines:

Extern "C" ( #include "MyCLib.h" )

Programs for older versions of Windows also used function calling conventions PASCAL language for functions Windows API. New programs should use the winapi modifier, which converts to _stdcall. Although this is not standard interface functions of C or C++, but it is precisely this one that is used to access Windows functions API. However, usually all this is already taken into account in the standard Windows headers.

Loading an Implicit DLL

When an application starts, it tries to find all DLL files implicitly attached to the application and place them in the RAM area occupied by the process. Finding DLL Files operating system is carried out in the following sequence.

  • The directory where the EXE file is located.
  • The current directory of the process.
  • Windows system directory.

If the DLL is not found, the application displays a dialog box indicating its absence and the paths searched. The process then shuts down.

If the required library is found, it is placed in RAM process, where it remains until its completion. The application can now access the functions contained in the DLL.

Dynamic loading and unloading of DLLs

Instead of having Windows dynamically link to the DLL when the application is first loaded into memory, you can link the program to the library module at runtime (with this method, you do not need to use the import library when creating the application). Specifically, you can determine which DLLs are available to the user, or allow the user to choose which DLLs will be loaded. This way you can use different DLLs that implement the same functions that perform various actions. For example, an application designed for independent data transfer will be able to decide at runtime whether to load a DLL for the TCP/IP protocol or for another protocol.

Loading a regular DLL

The first thing you need to do when dynamically loading a DLL is to place the library module in process memory. This operation done using the function ::LoadLibrary, which has a single argument - the name of the loaded module. The corresponding program fragment should look like this:

HINSTANCE hMyDll; :: if((hMyDll=:: LoadLibrary("MyDLL"))==NULL) ( /* failed to load the DLL */ ) else ( /* the application is allowed to use DLL functions through hMyDll */ )

Windows considers the standard library file extension to be .dll unless you specify a different extension. If the file name also includes a path, then only that path will be used to search for the file. Otherwise, Windows will search for the file in the same way as for implicitly included DLLs, starting from the directory from which the exe file is loaded and continuing according to the PATH value.

When Windows finds the file, its full path will be compared to the path of the DLLs already loaded by the process. If an identity is found, instead of downloading a copy of the application, a handle to the already included library is returned.

If the file is found and the library is loaded successfully, the function ::LoadLibrary returns its handle, which is used to access library functions.

Before using library functions, you must obtain their address. To do this, you must first use the directive typedef to define the type of a function pointer and define a variable of this new type, for example:

// the PFN_MyFunction type will declare a pointer to a function // that takes a pointer to a character buffer and produces a value of type int typedef int (WINAPI *PFN_MyFunction)(char *); ::PFN_MyFunction pfnMyFunction;

Then you should get a library descriptor, with the help of which you can determine the addresses of the functions, for example, the address of a function named MyFunction:

HMyDll=::LoadLibrary("MyDLL"); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,"MyFunction"); :: int iCode=(*pfnMyFunction)("Hello");

The function address is determined using the function ::GetProcAddress, it should be passed the library name and the function name. The latter must be transmitted in the form in which it is exported from the DLL.

You can also refer to a function by the ordinal number by which it is exported (in this case, a def file must be used to create the library, this will be discussed later):

PfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll, MAKEINTRESOURCE(1));

After finishing working with the dynamic link library, it can be unloaded from process memory using the function:: FreeLibrary:

::FreeLibrary(hMyDll);

Loading MFC extensions to dynamic libraries

When loading MFC extensions for DLLs (discussed in detail below) instead of functions LoadLibrary And FreeLibrary functions are used AfxLoadLibrary And AfxFreeLibrary. The latter are almost identical to the Win32 API functions. They only additionally guarantee that MFC structures initialized by the DLL extension have not been corrupted by other threads.

DLL Resources

Dynamic loading also applies to DLL resources that MFC uses to load standard application resources. To do this, you first need to call the function LoadLibrary and place the DLL in memory. Then using the function AfxSetResourceHandle you need to prepare the program window to receive resources from the newly loaded library. Otherwise, resources will be loaded from files attached to the process's executable file. This approach is convenient if you need to use different sets of resources, for example for different languages.

Comment. Using the function LoadLibrary You can also load executable files into memory (do not launch them for execution!). The executable module handle can then be used when calling functions FindResource And LoadResource to search and download application resources. Modules are also unloaded from memory using the function FreeLibrary.

Example of a common DLL and loading methods

Let's give source dynamic link library, which is called MyDLL and contains one function MyFunction, which simply prints a message.

First, a macro constant is defined in the header file EXPORT. Using this keyword When defining a function in a dynamic link library, it allows you to tell the linker that this function is available for use by other programs, causing it to include it in the import library. In addition, such a function, just like a window procedure, must be defined using the constant CALLBACK:

MyDLL.h#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str);

The library file is also somewhat different from regular C files for Windows. It contains instead of a function WinMain there is a function DllMain. This function is used to perform initialization, which will be discussed later. In order for the library to remain in memory after it is loaded so that its functions can be called, its return value must be TRUE:

MyDLL.c #include #include "MyDLL.h" int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) ( return TRUE; ) EXPORT int CALLBACK MyFunction(char *str) ( MessageBox(NULL,str,"Function from DLL",MB_OK); return 1; )

After translating and linking these files, two files appear - MyDLL.dll (the dynamic link library itself) and MyDLL.lib (its import library).

Example of implicit DLL connection by application

Let's now show the source code simple application which uses the MyFunction function from the MyDLL.dll library:

#include #include "MyDLL.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( int iCode=MyFunction("Hello"); return 0; )

This program looks like a regular Windows program, which is exactly what it is. However, it should be noted that in its source text, in addition to calling the MyFunction function from the DLL library, the header file of this library, MyDLL.h, is also included. It is also necessary to connect to it at the application composition stage import library MyDLL.lib (the process of implicitly connecting a DLL to an executable module).

It is extremely important to understand that the MyFunction code itself is not included in the MyApp.exe file. Instead, it simply contains a link to the MyDLL.dll file and a link to the MyFunction function that resides in that file. The MyApp.exe file requires the MyDLL.dll file to run.

The MyDLL.h header file is included in the MyApp.c program source file in the same way that the windows.h file is included there. Including the MyDLL.lib import library for linking is the same as including all Windows import libraries there. When MyApp.exe runs, it links to MyDLL.dll in the same way as all standard Windows dynamic link libraries.

Example of dynamically loading a DLL by an application

Let us now present the complete source code of a simple application that uses the MyFunction function from the MyDLL.dll library, using dynamic loading of the library:

#include typedef int (WINAPI *PFN_MyFunction)(char *); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( HINSTANCE hMyDll; if((hMyDll=LoadLibrary("MyDLL"))==NULL) return 1; PFN_MyFunction pfnMyFunction; pfnMyFunction=(PFN_MyFunction)Get ProcAddress(hMyDll ,"MyFunction"); int iCode=(*pfnMyFunction)("Hello"); FreeLibrary(hMyDll); return 0; )

Creating a DLL

Now, having become familiar with the principles of operation of DLLs in applications, let's look at how to create them. When developing an application, it is advisable to place functions that are accessed by several processes in a DLL. This allows for more efficient use of memory in Windows.

The easiest way to create a new DLL project is to use the AppWizard wizard, which automatically performs many of the operations. For simple DLLs, such as those discussed in this chapter, you must select the Win32 Dynamic-Link Library project type. The new project will be assigned all required parameters to create a DLL. Source files will have to be added to the project manually.

If you plan to fully use functionality MFC such as Documents and Views, or intend to create an OLE automation server, it is better to select the MFC AppWizard (dll) project type. In this case, in addition to assigning parameters to the project for connecting dynamic libraries, the wizard will do some additional work. The necessary links to MFC libraries and source files containing a description and implementation in the DLL of an application class object derived from CWinApp.

Sometimes it is convenient to first create a project like MFC AppWizard (dll) as a test application, and then create the DLL as part of it. As a result, the DLL will be created automatically if necessary.

DllMain function

Most DLLs are simply collections of essentially independent functions that are exported to and used by applications. In addition to the functions intended for export, each DLL has a function DllMain. This function is designed to initialize and clean up a DLL. It replaced the functions LibMain And WEP, used in previous Windows versions. Structure of the simplest function DllMain might look like this, for example:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) ( BOOL bAllWentWell=TRUE; switch (dwReason) ( case DLL_PROCESS_ATTACH: // Process initialization. break; case DLL_THREAD_ATTACH: // Thread initialization. break; case DLL_THREAD_DETACH: // Cleanup thread structures. break; case DLL_PROCESS_DETACH: // Clearing process structures. break; ) if(bAllWentWell) return TRUE; else return FALSE; )

Function DllMain called in several cases. The reason for calling it is determined by the dwReason parameter, which can take one of the following values.

When a DLL is first loaded by a process, the function is called DllMain with dwReason equal to DLL_PROCESS_ATTACH. Every time a process creates a new thread, DllMainO is called with dwReason equal to DLL_THREAD_ATTACH (except for the first thread, because in this case dwReason is equal to DLL_PROCESS_ATTACH).

After the process has finished working with the DLL, the function DllMain called with the dwReason parameter equal to DLL_PROCESS_DETACH. When a thread is destroyed (except the first), dwReason will be equal to DLL_THREAD_DETACH.

All initialization and cleanup operations for processes and threads that the DLL needs must be performed based on the dwReason value, as shown in the previous example. Process initialization is typically limited to the allocation of resources shared between threads, such as loading shared files and initializing libraries. Thread initialization is used to configure modes that are unique to this stream, for example to initialize local memory.

A DLL can contain resources that are not owned by the application that calls it. If DLL functions operate on DLL resources, it would obviously be useful to store an hInst handle somewhere out of the way and use it when loading resources from the DLL. The IpReserved pointer is reserved for internal using Windows. Therefore, the application should not claim it. You can only check its meaning. If the DLL was loaded dynamically, it will be NULL. When loading statically, this pointer will be non-zero.

If successful, the function DllMain should return TRUE. If an error occurs, FALSE is returned and further action is terminated.

Comment. If you don't write own function DllMain(), the compiler will include the standard version, which simply returns TRUE.

Exporting functions from DLLs

For an application to access dynamic library functions, each function must occupy a row in the table of exported DLL functions. There are two ways to add a function to this table at compile time.

__declspec(dllexport) method

You can export a function from a DLL by prefixing its description with the __declspec (dllexport) modifier. In addition, MFC includes several macros that define __declspec (dllexport), including AFX_CLASS_EXPORT, AFX_DATA_EXPORT, and AFX_API_EXPORT.

The __declspec method is not used as often as the second method, which works with module definition (.def) files, and allows for better control over the export process.

Module Definition Files

The syntax for .def files in Visual C++ is fairly straightforward, mainly because the complex options used in earlier versions of Windows no longer apply in Win32. As will become clear from the following simple example The .def file contains the name and description of the library, as well as a list of exported functions:

MyDLL.def LIBRARY "MyDLL" DESCRIPTION "MyDLL - example DLL library" EXPORTS MyFunction @1

In the function export line, you can indicate its serial number by placing the @ symbol in front of it. This number will then be used when contacting GetProcAddress(). In fact, the compiler assigns sequence numbers to all exported objects. However, the way it does this is somewhat unpredictable unless you assign these numbers explicitly.

You can use the NONAME parameter in the export string. It prevents the compiler from including the function name in the DLL export table:

MyFunction @1 NONAME

Sometimes this can save a lot of space in DLL file. Applications that use the import library to implicitly link DLLs will not "notice" the difference because implicit linking uses sequence numbers automatically. Applications that load DLLs dynamically will need to pass in GetProcAddress sequence number, not the function name.

When using the above, the def file describing the exported DLL functions may not be, for example, like this:

#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str); a like this: extern "C" int CALLBACK MyFunction(char *str);

Exporting classes

Creating a .def file to export even simple classes from a dynamic library can be quite difficult. You will need to explicitly export every function that can be used by an external application.

If you look at the memory allocation file implemented in the class, you will notice some very unusual features. It turns out that there are implicit constructors and destructors, functions declared in MFC macros, in particular _DECLARE_MESSAGE_MAP, as well as functions that are written by the programmer.

While it is possible to export each of these functions individually, there is an easier way. If you use the AFX_CLASS_EXPORT macromodifier in a class declaration, the compiler itself will take care of exporting the necessary functions that allow the application to use the class contained in the DLL.

Memory DLL

Unlike static libraries, which essentially become part of the application code, dynamic link libraries in 16-bit versions of Windows handled memory a little differently. Under Win 16, the DLL memory was located outside the task address space. Placing dynamic libraries in global memory provided the opportunity sharing their various tasks.

In Win32, a DLL resides in the memory area of ​​the process that loads it. Each process is given a separate copy of the "global" DLL memory, which is reinitialized each time a new process loads it. This means that the dynamic library cannot be shared in shared memory, as it was in Winl6.

Yet, by performing a series of intricate manipulations on a DLL's data segment, it is possible to create a shared memory area for all processes that use the DLL.

Let's say we have an array of integers that should be used by all processes that load a given DLL. This can be programmed as follows:

#pragma data_seg(".myseg") int sharedlnts ; // other public variables #pragma data_seg() #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

All variables declared between #pragma data_seg() directives are placed in the.myseg segment. The #pragma comment() directive is not a regular comment. It instructs the C runtime library to mark the new partition as read, write, and shareable.

Full DLL compilation

If the dynamic library project is created using AppWizard and the .def file is modified accordingly, this is enough. If you create project files manually or by other means without the help of AppWizard, you should include the /DLL parameter on the command line of the link editor. This will create a DLL instead of a standalone executable.

If there is a LIBRART line in the .def file, you do not need to explicitly specify the /DLL parameter on the link editor command line.

MFC has a number of special behavior regarding the dynamic library's use of MFC libraries. The next section is devoted to this issue.

DLL and MFC

The programmer is not required to use MFC when creating dynamic libraries. However, using MFC opens up a number of very important possibilities.

There are two levels of using the MFC structure in a DLL. The first one is a regular MFC-based dynamic library, MFC DLL(regular MFC DLL). It can use MFC, but cannot pass pointers to MFC objects between DLLs and applications. The second level is implemented in dynamic MFC extensions(MFC extensions DLL). Using this type of dynamic library requires some additional setup effort, but allows pointers to MFC objects to be freely exchanged between the DLL and the application.

Regular MFC DLLs

Regular MFC DLLs allow you to use MFC in dynamic libraries. However, applications that access such libraries do not necessarily have to be built on MFC. Regular DLLs can use MFC in any way, including creating new classes in the DLL based on MFC classes and exporting them to applications.

However, regular DLLs cannot exchange pointers to MFC-derived classes with applications.

If your application needs to exchange pointers to objects of MFC classes or their derivatives with a DLL, it must use the DLL extension described in the next section.

The architecture of regular DLLs is designed to be used by other programming environments, such as Visual Basic and PowerBuilder.

When creating a regular MFC DLL using AppWizard, a new project of type MFC AppWizard (dll). In the first dialog box of the Application Wizard, you must select one of the modes for regular dynamic libraries: "Regular DLL with MFC statistically linked" or "Regular DLL using shared MFC DLL". The first provides for static, and the second - dynamic connection of MFC libraries. You can later change the MFC to DLL connection mode using the combo box on the General tab of the Project settings dialog.

Managing MFC State Information

Each MFC process module contains information about its state. Thus, the state information of a DLL is different from the state information of the application that called it. Therefore, any functions exported from the library that are accessed directly from applications must tell MFC what state information to use. In a regular MFC DLL that uses MFC dynamic libraries, before calling any MFC routine, you must place the following line at the beginning of the exported function:

AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

This statement specifies the use of appropriate state information during the execution of the function that calls this subroutine.

Dynamic MFC Extensions

MFC allows you to create DLLs that are perceived by applications not as a set of separate functions, but as extensions of MFC. With this type of DLL, you can create new classes that derive from MFC classes and use them in your applications.

To allow pointers to MFC objects to be freely exchanged between an application and a DLL, you need to create a dynamic MFC extension. DLLs of this type connect to MFC dynamic libraries in the same way as any application that uses MFC dynamic extension.

To create a new dynamic MFC extension, the easiest way is to use the Application Wizard to assign the project type MFC AppWizard (dll) and in step 1 enable the "MFC Extension DLL" mode. As a result, the new project will be assigned all the necessary MFC dynamic extension attributes. In addition, a function will be created DllMain for a DLL, which performs a number of specific operations to initialize the DLL extension. Please note that dynamic libraries of this type do not and should not contain objects derived from CWinApp.

Initializing Dynamic Extensions

To fit into the MFC structure, dynamic MFC extensions require additional initial setup. The corresponding operations are performed by the function DllMain. Let's look at an example of this function created by the AppWizard wizard.

Static AFX_EXTENSION_MODULE MyExtDLL = ( NULL, NULL ) ; extern "C" int APIENTRY DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) ( if (dwReason == DLL_PROCESS_ATTACH) ( TRACED("MYEXT.DLL Initializing!\n") ; // Extension DLL one-time initialization AfxInitExtensionModule(MyExtDLL , hinstance) ; // Insert this DLL into the resource chain new CDynLinkLibrary(MyExtDLL); ) else if (dwReason == DLL_PROCESS_DETACH) ( TRACED("MYEXT.DLL Terminating!\n") ; ) return 1; // ok )

The most important part of this function is the call AfxInitExtensionModule. This is the initialization of a dynamic library, allowing it to work correctly as part of the MFC structure. The arguments of this function are the DLL library descriptor passed to DllMain and the AFX_EXTENSION_MODULE structure, which contains information about the dynamic library being connected to MFC.

There is no need to initialize the AFX_EXTENSION_MODULE structure explicitly. However, it must be declared. The constructor will handle the initialization CDynLinkLibrary. You need to create a class in the DLL CDynLinkLibrary. Its constructor will not only initialize the AFX_EXTENSION_MODULE structure, but will also add a new library to the list of DLLs that MFC can work with.

Loading dynamic MFC extensions

Starting with version 4.0, MFC allows you to dynamically load and unload DLLs, including extensions. To correctly perform these operations on the created DLL in its function DllMain at the moment of disconnection from the process, you need to add a call AfxTermExtensionModule. The last function is passed the AFX_EXTENSION_MODULE structure already used above as a parameter. To do this in the text DllMain you need to add the following lines.

If(dwReason == DLL_PROCESS_DETACH) ( AfxTermExtensionModule(MyExtDLL); )

Also, keep in mind that the new DLL is a dynamic extension and must be loaded and unloaded dynamically using functions AfxLoadLibrary And AfxFreeLibrary,but not LoadLibrary And FreeLibrary.

Exporting functions from dynamic extensions

Let's now look at how functions and classes from a dynamic extension are exported to an application. Although you can manually add all extended names to the DEF file, it is better to use modifiers for declarations of exported classes and functions, such as AFX_EXT_CLASS and AFX_EXT_API, for example:

Class AFX_EXT_CLASS CMyClass: public CObject (// Your class declaration ) void AFX_EXT_API MyFunc() ;