Создание библиотек программирования
Создание библиотек программирования в общем напоминает создание обычных приложений, однако есть определённые тонкости.

В заголовочном файле библиотеки описываются ресурсы, предназначенные для экспорта (то есть для использования программами). Эти ресурсы называются интерфейсом библиотеки (или программным интерфейсом библиотеки – API от Application Programming Interface). Пример заголовочного файла статической библиотеки, описывающей работу с комплексными числами, представлен на листинге 1. Как видно, это обычное объявление класса.
Листинг 1. Заголовочный файл библиотеки комплексных чисел.
#ifndef COMPLEX_H
#define COMPLEX_H
#include <iostream>
#include <string>
class complex{
    double _real, _imaginary;
public:
    complex(const double& = 0, const double& = 0);
    complex(const complex&);
    ~complex();
    
    void operator +=(const complex&);
    void operator -=(const complex&);
    void operator *=(const complex&);
    void operator /=(const complex&);
    
    complex& operator = (const complex&);
    complex& operator = (const double&);
    
    const double& real() const;
    const double& imaginary() const;
    void swap(complex&);
    std::string toString() const;
    double module() const;
    complex conjugate() const;
};
complex operator + (const complex&, const complex&);
complex operator - (const complex&, const complex&);
complex operator * (const complex&, const complex&);
complex operator / (const complex&, const complex&);
std::ostream& operator << (std::ostream&, const complex&);
bool operator  == (const complex&, const complex&);
complex pow(const complex&, const complex&);
double abs(const complex&);
#endif	/* COMPLEX_H */Заголовочные файлы могут включаться в проект несколько раз. Это может вызывать избыточное определение типов данных и переменных, что будет приводить к ошибкам компиляции. Именно поэтому описание ресурсов библиотек в заголовочных файлах заключается между директивами препроцессора
#ifndef CONSTANT_NAME
#define CONSTANT_NAME
//…
#endifЭти директивы на этапе препроцессирования программного кода позволяют включать определения ресурсов только один раз.
Компиляторы фирмы Microsoft (и Intel) позволяют использовать вместо конструкции ветвления препроцессора конструкцию
#pragma onceЧто является предписанием компилятору, что данный файл подключается только раз.
Реализация объявленных в заголовочном файле ресурсов происходит, как и обычно, в файле cpp.
Листинг 2. Реализация класса комплексных чисел.
#include "complex.h"
#include <sstream>
complex::complex(const double& r, const double& im): _real(r), _imaginary(im){}
complex::complex(const complex& c): _real(c._real), _imaginary(c._imaginary){}
complex::~complex(){}
void complex::operator +=(const complex& c){
    _real += c._real;
    _imaginary += c._imaginary;
}
void complex::operator -=(const complex& c){
    _real -= c._real;
    _imaginary -= c._imaginary;
}
void complex::operator *=(const complex& c){
    complex tmp;
    tmp._real = _real*c._real - _imaginary*c._imaginary;
    tmp._imaginary = _real*c._imaginary + _imaginary*c._real;
    swap(tmp);
}
void complex::operator /=(const complex& c){
    complex tmp;
    tmp = (*this) * c.conjugate();
    _real = tmp._real / c.module();
    _imaginary = tmp._imaginary / c.module();
}
complex& complex::operator =(const complex& c){
    _real = c._real;
    _imaginary = c._imaginary;
    return *this;
}
complex& complex::operator =(const double& d){
    _real = d;
    _imaginary = 0;
    return *this;
}
const double& complex::real() const { return _real; }
const double& complex::imaginary() const { return _imaginary; }
void complex::swap(complex& c){
    complex tmp(c);
    c._imaginary = _imaginary;
    c._real = _real;
    _imaginary = tmp._imaginary;
    _real = tmp._real;
}
std::string complex::toString() const{
    std::ostringstream strcout;
    strcout << _real << " + i*( " << _imaginary << " )";
    return strcout.str();
}
double complex::module() const{
    return _real*_real + _imaginary*_imaginary;
}
complex complex::conjugate() const{
    return complex(_real, -_imaginary);
}
// extern class interface
complex operator + (const complex& c1, const complex& c2){
    complex tmp(c1);
    tmp += c2;
    return tmp;
}
complex operator - (const complex& c1, const complex& c2){
    complex tmp(c1);
    tmp += c2;
    return tmp;
}
complex operator * (const complex& c1, const complex& c2){
    complex tmp(c1);
    tmp += c2;
    return tmp;
}
complex operator / (const complex& c1, const complex& c2){
    complex tmp(c1);
    tmp += c2;
    return tmp;
}
std::ostream& operator << (std::ostream& os, const complex& c){
    os << c.toString();
    return os;
}
bool operator  == (const complex& c1, const complex& c2){
    return ((c1.real() == c2.real()) && (c1.imaginary() == c2.imaginary()));
}
complex pow(const complex&, const complex&){
    complex result;
    return result;
}
double abs(const complex& c){
    return c.module();
}Особенности создания динамических библиотек
В случае разработки динамических библиотек, их создание может происходить аналогично созданию статических библиотек. Особенностью, в данном случае, будет только выбор типа проекта. В этом случае библиотека может использоваться неявно (то есть, через подключение заголовочного файла и указание использования файла импорта библиотеки). Для использования функций библиотеки необходимо явно указать, к каким функциям может получить доступ зависимая от данной библиотеки программа. Для этого используется специальная конструкция языка __declspec(dllexport), указывающая, что функция (класс) доступна для использования. В листинге 3 представлен пример заголовочного файла, в котором описан класс комплексного числа. Этот класс предназначен на экспорт.
Листинг 3. Экспорт классов и функций.
#pragma once
#include <iostream>
#include <string>
#define DLLEXPORT __declspec(dllexport)
class DLLEXPORT complex
{
    double _real, _imaginary;
public:
    complex(const double = 0, const double = 0);
    complex(const complex&);
    ~complex(void);
    complex& operator = (const complex&);
    const double& real() const;
    const double& imaginary() const;
    const std::string toString() const;
    void operator += (const complex&);
};
DLLEXPORT bool operator == (const complex&, const complex&);
DLLEXPORT complex operator + (const complex&, const complex&);
DLLEXPORT std::ostream& operator << (std::ostream&, const complex&);
DLLEXPORT int min(int, int);Также функции на экспорт можно определить при помощи def-файла (файл определения модуля), что позволяет не использовать конструкцию __declspec(dllexport). Файл определения модуля описывается по следующим правилам:
- Первая строка файла есть указание имени библиотеки, при помощи инструкции LIBRARY. 
- Вторая секция открывается инструкцией EXPORTS, она указывает, какие части библиотеки идут на экспорт. Каждая следующая строка представляет имя функции и порядковый номер в библиотеке, выделенный символом @. 
#ifndef EXTMATH_H
#define EXTMATH_H
size_t gcd (size_t, size_t);
bool is_prime(size_t);
bool is_odd(size_t);
#endif#include "extmath.cpp"
size_t gcd (size_t a, size_t b){
    if(a == 0){
        return b;
    }
    while(b != 0){
        size_t tmp = a % b;
        a = b;
        b = tmp;
    }
    return a;
}
bool is_prime(size_t p){
    size_t i = 2;
    size_t top = p / 2;
    while(i <= top){
        if(p / i == 0){
            return false;
        }
        ++ i;
    }
    return true;
}
bool is_odd(size_t p){
    return (p % 2 == 0);
}Листинг 4. Пример def-файла
LIBRARY  EXTMATH
EXPORTS
            gcd          @1
            is_prime     @2
            is_odd       @3Описанный в листингах 2 и 3, класс можно использовать только в С++ проектах. Для создания библиотек, совместимых с языком С, необходимо заголовочные файлы писать в стиле С (без использования классов, шаблонов и др. специфичные для С++ конструкции). Также желательно в макросе DLLEXPORT приписать модификатор extern ”C” – это позволяет, при компиляции библиотеки, сохранить оригинальные имена экспортируемых ресурсов (некоторые компиляторы «кодируют» имена функций и классов).
Использование динамической библиотеки, созданной таким образом, не будет возможно в других, отличных от С/С++, языках программирования. Это связанно с тем, что в языках Pascal, Basic и др. функции читают входные параметры слева направо, тогда как в С/С++ входные параметры читаются справа налево. Для создания совместимой с этой группой языков библиотеки используется модификатор __stdcall (или эквивалент __pascal), который меняет порядок чтения входных параметров функции.
В OS Windows динамические библиотеки могут иметь также точку входа - функцию DllMain:
#include <windows.h> BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call){ case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
Сборка библиотек
Самый простой способ создания библиотек - выбор соответстувующего проекта в среде программирования. Все современные среды программирования предлагают проекты как статических, так и динамических библиотек.
Разработка статических библиотек ничем не отличается от написания программы, только в статических библиотеках нет функции main.
Last updated
