📜 ⬆️ ⬇️

How to ensure proper intersection of dynamic library boundaries using custom smart pointer removal tools

Many C ++ experts agitate to use smart pointers, arguing that from modern C ++, the explicit use of new should disappear altogether (well, at least when the absence of std::make_unique in C ++ 14). All dynamic memory allocations must be encapsulated in either the standard library, or containers of type std::vector , or smart pointers.

Smart pointers of the standard library can be configured so that they themselves are engaged in the release of their memory. This possibility is the basis of the answer to the question posed in the title of the article.

An object is intersecting the dynamic library boundary if it is initialized in one block and used in another. This happens when, for example, an object is initialized in a dll and a pointer to it is returned.
')
Suppose one library (or an executable module) is associated with another library, using a factory to dynamically initialize an object and get a pointer to it. The block that uses this pointer can delete the pointer to free the memory area it points to. If a library that allocates memory and a block working with a pointer use different versions of the OS dynamic allocation (CRT in Windows), an error will occur. An example of this problem (in the case of Windows):

As a rule (before the advent of C ++ 11), library developers had to develop functions to free memory for objects that were allocated within this library, in order to avoid this problem. This had a side effect: the interfaces of such libraries became more “heavy”, besides, they now needed “know-how” to correctly allocate and free memory for library objects. Ideally, the user should not have been disturbed by the allocation / release scheme itself, it simply had to call the library mechanism (for example, the factory) to allocate memory, without worrying about its subsequent release.

Go to Codding


We will have two projects: the first will simply consist of a main file, using the library factory to initialize objects from it, the second will illustrate the problem situation and its solution.
The problem area is the singleton factory (ProblematicFactory), which initializes the object and returns a pointer to it. The solution is another one that, after initializing the object, returns a pointer std::unique_ptr , which has its own deletion tool, which releases the memory to the DLL.
If you run the program in debug mode with the definition of USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION , you can see that the debugger detects heap corruption.

Main file

 // main.cpp #include <ProblematicFactory.h> #include <SafeFactory.h> //  undef  define,   assert'    #undef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION int main() { #ifdef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION { //     DLL auto wMyObject = ProblematicFactory::getInstance().create(); //       delete wMyObject; //  DLL           CLR DLL, //   ,  -     } #endif { auto wMyObject = SafeFactory::getInstance().create(); //      , wMyObject  //  ,    , //   MyClass.h (. ),   //      } { std::shared_ptr< MyClass > wMyObject = SafeFactory::getInstance().create(); } return 0; } 

Problem factory

This is a typical implementation of a factory that returns a pointer to an object that can be created by the library.
 // ProblematicFactory.h #pragma once #include "DllSwitch.h" #include "MyClass.h" class LIBRARYFACTORY_API ProblematicFactory { public: static ProblematicFactory & getInstance() { static ProblematicFactory wProblematicFactory; return wProblematicFactory; } MyClass * create() const { return new MyClass; } private: ProblematicFactory() {}; }; 

Safe factory

Syntactically, the use of this factory is exactly the same as the problem one (see main), but here the pointer is encapsulated in std::unique_ptr , and not std::shared_ptr .
 // SaveFactory.h #pragma once #include "DllSwitch.h" #include "MyClass.h" #include <memory> class LIBRARYFACTORY_API SafeFactory { public: static SafeFactory & getInstance(); //  std::unique_ptr     , // ..       DLL.   //   ,   std::unique_ptr . //  ,      // ,    MyClass  std::default_delete inline std::unique_ptr< MyClass > create() const { return std::unique_ptr< MyClass >(doCreate()); } private: SafeFactory(); MyClass * doCreate() const; }; 

Crossing the border

 // MyClass.h #pragma once #include "DllSwitch.h" #include <memory> class LIBRARYFACTORY_API MyClass { }; namespace std { template<> class LIBRARYFACTORY_API default_delete< MyClass > { public: void operator()(MyClass *iToDelete) { delete iToDelete; } }; } 

In all the above files, the header file DllSwitch.h , which defines LIBRARYFACTORY_API , its contents:
 // DllSwitch.h #pragma once #ifdef LIBRARYFACTORY_EXPORTS #define LIBRARYFACTORY_API __declspec(dllexport) #else #define LIBRARYFACTORY_API __declspec(dllimport) #endif 


UPD All implementations of functions must be placed in separate files.

Source: https://habr.com/ru/post/182970/


All Articles