📜 ⬆️ ⬇️

DynLib: library for creating and working with DLL

image The DynLib library provides convenient tools for developers using intermodule interaction (EXE <-> DLL, DLL <-> DLL) in their projects, and significantly reduces the time and amount of code.
DynLib has become an integral development tool. Under the cut share results.




Disadvantages of the traditional approach to the implementation of DLL

The main disadvantages of the traditional approach (implementation) include:
  1. inability to use namespaces
  2. a large amount of service code required:
    • when implementing dynamic library loading;
    • when implementing intermodular interaction through classes, through the use of decryptors (or other implicit structures) and wrapper classes;
    • when implementing error return mechanisms, in the case when exported functions can generate exceptions.
These problems are solved with the help of the library DynLib!

DynLib usage examples


1. Using a regular DLL


Task. Dynamically connect and use the library test_lib.dll, which implements simple mathematical operations, with the interface presented in the header file:
//========== test_lib.h ========== #pragma once extern "C" __declspec(dllexport) int __stdcall sum(int x, int y); extern "C" __declspec(dllexport) int __stdcall mul(int x, int y); extern "C" __declspec(dllexport) double __stdcall epsilon(); 

Decision. You need to write the following header file and connect it to the project.
 //========== test_lib.hpp ========== #pragma once #include <dl/include.hpp> DL_NS_BLOCK(( test ) ( DL_C_LIBRARY( lib ) ( ( int, __stdcall, (sum), (int,x)(int,y) ) ( int, __stdcall, (mul), (int,x)(int,y) ) ( double,__stdcall, (epsilon), () ) ) )) 
The preprocessor will generate a test :: lib class that performs dynamic DLL loading and contains the listed functions sum , mul, and epsilon . To connect the DLL to the application, you must include the provided header file test_lib.hpp in the source code. Next, create an object of the class test :: lib . Access to the exported DLL functions is possible via '.' or '->'.
 //========== exe.cpp ========== #include "test_lib.hpp" int main() { test::lib lib( "path_to/test_lib.dll" ); int s = lib->sum( 5, 20 ); int m = lib.mul( 5, 10 ); double eps = lib.epsilon(); return 0; } 

2. Creating calculator.dll library


Task. Write a library calculator.dll, which should calculate the sum, the product of two numbers and the value of the square root. Dynamically load the library and call each function.
Decision
 //========== calculator.hpp ========== #include <dl\include.hpp> DL_NS_BLOCK(( team ) ( DL_LIBRARY( calculator ) ( ( double, sum, (double,x)(double,y) ) ( double, mul, (double,x)(double,y) ) ( double, sqrt, (double,x) ) ) )) //========== calculator_dll.cpp ========== #include "calculator.hpp" struct calculator { static double sum( double x, double y ) { return x + y; } static double mul( double x, double y ) { return x * y; } static double sqrt( double x ) { return std::sqrt(x); } }; DL_EXPORT( team::calculator, calculator ) 

Use dll
 //========== application.cpp ========== #include <iostream> #include "calculator.hpp" int main() { using namespace std; team::calculator calc( "calculator.dll" ); cout << "sum = " << calc.sum(10, 20) << endl; cout << "mul = " << calc.mul(10, 20) << endl; cout << "sqrt = " << calc.sqrt(25) << endl; return 0; } 

3. Modernization of the calculator.dll library. The use of exceptions.


Task. The sqrt square root calculation function in the calculator.dll library should return an error if the input value is incorrect.
Decision
 //========== calculator.hpp ========== #include <dl\include.hpp> DL_NS_BLOCK(( team ) ( DL_LIBRARY( calculator ) ( ( double, sqrt, (double,x) ) ) )) //========== calculator_dll.cpp ========== #include "calculator.hpp" struct calculator { static double sqrt( double x ) { if ( x < 0 ) throw std::invalid_argument( "   0" ); return std::sqrt( x ); } }; DL_EXPORT( team::calculator, calculator ) 

')
Use dll
 //========== application.cpp ========== #include <iostream> #include <locale> #include "calculator.hpp" int main() { using namespace std; locale::global( locale("", locale::ctype) ); try { team::calculator calc( "calculator.dll" ); cout << "sqrt1 = " << calc.sqrt( 25 ) << endl; cout << "sqrt2 = " << calc.sqrt( -1 ) << endl; } catch (dl::method_error const& e) { cerr << "what: " << e.what() << endl; } return 0; } //==========   ========== 
 sqrt1 = 5 what: exception 'class std::invalid_argument' in method 'sqrt' of class '::team::calculator' with message '   0' 

4. Implementation of the shapes.dll library. The use of interfaces.


Task. Create a shapes.dll library for working with geometric shapes (square, rectangle, circle). All figures must support a common interface through which you can find out the coordinates of the center of the figure.
Decision
 //========== shapes.hpp ========== #include <dl/include.hpp> DL_NS_BLOCK(( shapes ) ( DL_INTERFACE(figure) ( ( char const*, name, ) ( double, center_x, ) ( double, center_y, ) ( void, center_xy, (double&,x)(double&,y) ) ) )) DL_NS_BLOCK(( shapes ) ( DL_LIBRARY(lib) ( ( shapes::figure, create_rectangle, (double,left)(double,top)(double,width)(double,height) ) ( shapes::figure, create_square, (double,left)(double,top)(double,size) ) ( shapes::figure, create_circle, (double,center_x)(double,center_y)(double,radius) ) ) )) //========== shapes_lib.cpp ========== #include "shapes.hpp" class rectangle { public: rectangle(double l, double t, double w, double h) : l_(l), t_(t), w_(w), h_(h) { if (w < 0) throw std::invalid_argument( "   " ); if (h < 0) throw std::invalid_argument( "   " ); } char const* name() { return "rectangle"; } double center_x() { return l_ + w_ / 2.; } double center_y() { return t_ + h_ / 2.; } void center_xy(double& x, double& y) { x = center_x(); y = center_y(); } private: double l_, t_, w_, h_; }; class square { public: square(double l, double t, double s) : l_(l), t_(t), s_(s) { if (s < 0) throw std::invalid_argument( "    " ); } char const* name() { return "square"; } double center_x() { return l_ + s_ / 2.; } double center_y() { return t_ + s_ / 2.; } void center_xy(double& x, double& y) { x = center_x(); y = center_y(); } private: double l_, t_, s_; }; class circle { public: circle(double x, double y, double r) : x_(x), y_(y), r_(r) { if (r < 0) throw std::invalid_argument( "   " ); } char const* name() { return "circle"; } double center_x() { return x_; } double center_y() { return y_; } void center_xy(double& x, double& y) { x = x_; y = y_; } private: double x_, y_, r_; }; struct shapes_lib { static shapes::figure create_rectangle( double l, double t, double w, double h ) { return dl::shared<rectangle>( l, t, w, h ); } static shapes::figure create_square( double l, double t, double s ) { return dl::shared<square>( l, t, s ); } static shapes::figure create_circle( double x, double y, double r ) { return dl::shared<circle>( x, y, r ); } }; DL_EXPORT( shapes::lib, shapes_lib ) //========== application.cpp ========== #include <iostream> #include "shapes_lib.hpp" void print_center( shapes::figure shape ) { std::cout << shape.name() << ": " << shape.center_x() << "-" << shape.center_y() << std::endl; } int main() { shapes::lib lib( "shapes.dll" ); print_center( lib.create_circle(10, 10, 10) ); print_center( lib.create_square(0, 0, 20) ); print_center( lib.create_rectangle(0, 5, 20, 10) ); return 0; } 

How to connect the library


The library is supplied as header files. No .lib and .dll is required. To connect, you need to add the following directive:
 #include <dl/include.hpp> 

Library items


Many classes and macros of the DynLib library can be used independently and separately from each other.

DL_BLOCK

Serves as a container for all other macros.
 DL_BLOCK ( // declarations ) 



DL_NS_BLOCK

Serves as a container for all other macros. Creates namespaces for a class.
 DL_NS_BLOCK( (ns0, ns1, ns2 … )/* ,  10*/ ( // declarations )) 

Macros that are described below except DL_EXPORT should be placed in DL_BLOCK or DL_NS_BLOCK

DL_C_LIBRARY
The purpose of the macro is to provide the user with a ready-made class that implements dynamic DLL loading and automatic import of functions. Macros are represented as:
 DL_C_LIBRARY(lib_class) ( /*functions*/ ( ret_type, call, (name, import_name), arguments ) ) 

Classes generated by the DL_C_LIBRARY macro cannot be passed across DLL boundaries
DL_RECORD

The macro DL_RECORD generates a packed data structure for use in intermodule communication. In addition, a constructor is created with all the arguments listed in the macro.
 DL_RECORD( record_name ) ( /*fields*/ (type, name, =default_value) ) 

Example:

 //========== some_exe.cpp ========== #include <dl/include.hpp> DL_BLOCK ( DL_RECORD( data ) ( ( int, x ) ( int, y, = 100 /*  */ ) ( int, z, = 200 /*  */ ) ) ) int main() { data v( 20 ); // x = 20, y = 100, z = 200 vx = 10; vy = vx; vz = 50; v = data( 5, 20, 30 ); data a( 1, 2, 3 ); return 0; } 


DL_LIBRARY

The macro DL_LIBRARY performs several tasks:
  1. acts as a description (documentation) of the interface between the EXE (DLL) and DLL;
  2. contains the necessary structures for the automatic export of library functions to the developer;
  3. implements a class that provides loading of a DLL with a specified interface and provides access to exported functions by the user;
  4. Provides correct use of C ++ exceptions:
      - automatic interception of C ++ exceptions on the DLL side;
    	   - returning the value across the DLL boundaries, signaling the presence of an exception;
    	   - the generation of a new exception in case the exception on the DLL side was intercepted (with the recovery of the description and information about the type of exception).
    	
 DL_LIBRARY( name ) ( /*functions*/ ( ret_type, name, arguments ) ) 

Classes generated by the DL_LIBRARY macro cannot be passed across DLL boundaries.
To demonstrate the operation of the macro, we will present the following header file:
 //========== test1_lib.hpp ========== #pragma once #include <dl/include.hpp> DL_NS_BLOCK(( team, test ) ( DL_LIBRARY( lib ) ( ( int, sum, (int,x)(int,y) ) ( void, mul, (int,x)(int,y)(int&,result) ) ( double, epsilon, () ) ) )) 

This description is used by the DLL developer to export functions via the DL_EXPORT macro. By connecting the test1_lib.hpp header file, a user can immediately start working with a DLL:
 //========== test1_exe.cpp ========== #include <test1_lib.hpp> int main() { team::test::lib lib( "test1.dll" ); int s = lib.sum( 5, 10 ); lib.mul( 5, 5, s ); double eps = lib->epsilon(); return 0; } 

DL_EXPORT

The macro DL_EXPORT is intended for exporting DLL functions.
DL_EXPORT ( lib_class , lib_impl_class )To export DLL functions you need:
  1. Create a class (structure);
  2. Define each function from the interface as static. Functions must be in the public scope :;
  3. Export the functions by writing the DL_EXPORT (lib, impl) construct.
For example, we will present the implementation of the DLL for the interaction interface in the test1_lib.hpp file defined in the DL_LIBRARY description.
 //========== test1_dll.cpp ========== #include "test1_lib.hpp" struct lib_impl { static int sum( int x, int y ) { return x + y; } static void mul( int x, int y, int& result ) { result = x + y; } static double epsilon() { return 2.0e-8; } }; DL_EXPORT( team::test::lib, lib_impl ) 

DL_INTERFACE

The macro allows to describe the class interface and provide the user with a wrapper class for working with it. The implementation of the wrapper class ensures the correct use of C ++ exceptions:
  - automatic interception of C ++ exceptions on the DLL side;
	  - returning the value across the DLL boundaries, signaling the presence of an exception;
	  - the generation of a new exception in case the exception on the DLL side was intercepted (with the recovery of the description and information about the type of exception).
	
The wrapper class generated by this macro has a shared ownership of the object that implements this interface. Shared ownership is provided by a reference counting mechanism, i.e. when a wrapper class object is copied, an internal function is called to increase the reference count, and when destroyed, an internal function to reduce the reference count. When the counter reaches 0, the object is automatically deleted. Access to interface methods is done via '.' or '->'.
DynLib library ensures safe use of interface classes at the border of an EXE (DLL) <-> DLL

 DL_INTERFACE( interface_class ) ( /*methods*/ ( ret_type, name, arguments ) ) 
Example:
 DL_NS_BLOCK(( example ) ( DL_INTERFACE( processor ) ( ( int, threads_count, () ) ( void, process, (char const*,buf)(std::size_t,size) ) ) )) 

Using:
  example::processor p; p =… // .  dl::shared  dl::ref int tcount = p->threads_count(); p.process(some_buf, some_buf_size); 

dl :: shared

The template class dl :: shared <T> solves the following tasks:
  1. dynamic creation of an object of class T with arguments passed to the constructor;
  2. adding a reference count and securing shared ownership (like boost (std) :: shared_ptr);
  3. implicit coercion to the class object generated by the DL_INTERFACE macro.
Access to member functions of class T is done via '->'.
The dl :: shared classes cannot be passed across DLL boundaries .
Suppose there is a class my_processor and the interface example :: processor :
 class my_processor { public: my_processor( char const* name = "default name" ); int threads_count(); void process(char const* buf, std::size_t size); private: //   }; DL_NS_BLOCK(( example ) ( DL_INTERFACE( processor ) ( ( int, threads_count, () ) ( void, process, (char const*,buf)(std::size_t,size) ) ) )) 

Examples of using dl :: shared are presented below:
 dl::shared<my_processor> p1( "some processor name" ); //   my_processor   dl::shared<my_processor> p2; //   my_processor   c    dl::shared<my_processor> p3( p1 ); // p3  p1       ,   = 2 dl::shared<my_processor> p4( dl::null_ptr ); // p4      p3.swap( p4 ); // p4    ,   p1, p3 —      p4 = dl::null_ptr; // p4      p2 = p1; // p2    p1 p2 = p1.clone(); //    my_processor //   my_processor      p2->threads_count(); p2->process( /*args*/ ); //   my_processor example::processor pi = p2; //   my_processor   example::processor // pi     ,      ,   . pi->threads_count(); pi->process(/*args*/); //   my_processor   pi. 

dl :: ref

A library function that allows you to cast any object to an interface class object declared via DL_INTERFACE with an identical set of methods. Typically, this behavior is necessary when there is a function that takes a class-interface as an argument, and it should pass an object placed on the stack.
It is necessary to use the dl :: ref function with caution, since the objects of the interface classes, in this case, will not own the transferred objects, and the user controls the lifetime of the object and its use through the interface classes. Copying objects of interface classes that refer to objects passed through dl :: ref is allowed and quite correct (since there is no reference counter, then there is nothing to change — objects of interface classes know how to work correctly here).
 class my_processor { public: my_processor( char const* name = "default name" ); int threads_count(); void process( char const* buf, std::size_t size ); private: //   }; DL_NS_BLOCK(( example ) ( DL_INTERFACE( processor ) ( ( int, threads_count, () ) ( void, process, (char const*,buf)(std::size_t,size) ) ) )) void some_dll_func( example::processor p ) { //  p } int main() { my_processor processor( "abc" ); some_dll_func( dl::ref(processor) ); //       ,   dl::object<my_processor> return 0; } 

Supported Compilers


DynLib is fully compatible with the following compilers (development environments):Partially compatible with the following compilers (development environments):You can get the library here.

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


All Articles