Документ

Help >
Документ

В данной справке собрано множество статей по созданию и использованию Dll.

Документ создан специально для сайта gamedev.clan.su создай свою игру с нами!.

Copyright © 2018. All rights reserved. (To change the copyright info, just edit it in template.)


Как правильно создавать DLL на C++

Как правильно создавать DLL на C++

Как видно из названия, в этом посте я расскажу о том, как создать DLL на C++ наиболее удобным и практичным способом, чтобы функции, помещенные туда, можно было легко и свободно использовать в других программах.

Как наверное многим известно, чтобы после компиляции создалась DLL, а не exe-файл, достаточно указать в настройках компиляции, что мы хотим получить именно Dynamic Link Library. Однако, не все так просто. По крайней мере, при использовании языка C/C++ и если нам нужно, чтобы потом нашим творением (нашей DLL) могли спокойно пользоваться другие разработчики, которые, ко всему прочему, могут использовать и другие компиляторы для сборки exe-файла.

Для этого функцию надо объявить как экспортируемую из DLL. Делается это с помощью добавления слева от функции модификатора extern «C». В итоге, функция будет выглядеть примерно так:

1
2
3
4
extern "C"
VOID CALLBACK MyFunction(HDC hdc, LPRECT prc)
{
}

К слову вместо extern «C» можно (и наверное даже лучше) написать EXTERN_C. Именно так это выражение определено в WinNT.h.

Вы спросите, а в чем проблема? А проблема в том, что компилятор C++ искажает имена функций, при добавлении их в DLL, добавляя к её имени спереди символ подчеркивания и в конце – символ «@» и число байт, передаваемых функции в качестве параметра. Самое забавное, что этим же грешит Microsoft C, даже если не использовать при написании программы C++. И искажает он только имена функций, экспортирующихся по соглашению __stdcall, т.е. по тому же самому, что используется в WIN API.
Для того, чтобы гарантированно избежать проблем с искажением имен и вытекающими из нее, следует добавить к проекту *.def файл (обычный текстовый файл, но с расширением def), в котором указать примерно следующее:

LIBRARY «MyName»

EXPORTS
MyFunction

Где “dll” – имя нашей библиотеки, и после EXPORTS перечислены имена экспортируемых из нее функций.
Полный синтаксис .def файла выглядит так:

entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]

Где entryname – это имя функции или переменной, которую необходимо экспортировать. Если нужно, чтобы экспортируемое имя отличалось от имени функции, используемого в DLL, то следует указать сначала желаемое имя, а через равно – имя в DLL:

EXPORTS
func2=func1

@ordinal позволяет указать, что не номер, а имя функции попадет в таблицу экспорта библиотеки DLL.
Подробнее об остальных ключевых словах в файле .def можно прочитать в MSDN.

Там же рекомендуют использовать ключевое слово __declspec(dllexport) в коде. Однако, следует отметить, что тогда эти методы не смогут вызываться программами, написанными на чистом C, так что от extern “C” видимо не уйти.


Создание и использование DLL (Microsoft Visual C++).

Создание и использование DLL (Microsoft Visual C++).

Введение

Создать DLL намного легче, чем вы думаете. Библиотеки DLL очень полезны, если вы программируете под Windows. В этих библиотеках обычно хранят часто используемые подпрограммы. В статье рассказывается как создать простую DLL и показано, как вызвать функции, содержащиеся в ней. Примеры расчитаны на использование Microsoft Visual C++ 6.0, но нетрудно перевести их на любой диалект C++.

Создание DLL

Ничего особенного здесь нет. Как обычно, вы просто пишите функции, как в обычной программе. Если вы используете MSVC, создайте новый проект и укажите, что вы создаете Win32 Dynamic-Link Library. После компиляции вы получите DLL, библиотеку импорта (.lib) и библиотеку экспорта (.exp). Далее показан примерный код вашей DLL:

Заголовочный файл (DLLTEST.H)

#ifndef _DLLTEST_H_
#define _DLLTEST_H_

#include <iostream.h>
#include <stdio.h>
#include <windows.h>

extern "C" __declspec(dllexport) void NumberList();
extern "C" __declspec(dllexport) void LetterList();

#endif

Код библиотеки (DLLTEST.CPP)

#include "dlltest.h"

#define MAXMODULE 50

char module[MAXMODULE];

extern "C" __declspec(dllexport) void NumberList()
{
    GetModuleFileName(NULL, (LPTSTR)module, MAXMODULE);
    cout << "\n\nThis function was called from "
    << module
    << endl << endl;
    cout << "NumberList(): ";
    for(int i=0; i<10; i++)
    {
       cout << i << " ";
    }
    cout << endl << endl;
}

extern "C" __declspec(dllexport) void LetterList()
{
    GetModuleFileName(NULL, (LPTSTR)module, MAXMODULE);
    cout << "\n\nThis function was called from "
    << module
    << endl << endl;
    cout << "LetterList(): ";
    for(int i=0; i<26; i++)
    {
       cout << char(97 + i) << " ";
    }
    cout << endl << endl;
}

Как видите, ничего особенного в коде нет. Приложение, используемое для примера - консольное, так что здесь просто запрограммированы две функции, выводящие текст. Строка

extern "C" __declspec(dllexport)

означает, что функция будет видна вне DLL (т.е. ее можно вызывать из нашей программы).
После компиляции мы получим библиотеку. Теперь посммотрим, как ее можно использовать.

Использование DLL (с библиотекой импорта)

Сначала посмотрим, как использовать DLL вместе с библиотекой импорта (dlltest.lib), которая получается при компиляции предыдущего примера. Этот метод очень прост, так как в таком случае нужно просто включить заголовочный файл библиотеки и саму библиотеку в проект. Пример:

Исходный файл приложения, использующего библиотеку DLL (DLLRUN01.EXE)

#include <conio.h>
#include <dlltest.h>

void main()
{
    NumberList();
    LetterList();

    getch();
}

Это будет прекрасно работать, если у вас есть заголовочный файл и библиотека импорта (dlltest.lib) находится в каталоге, прописанном в библиотечных путях. Перед запуском приложения, убедитесь, что DLL находится в каталоге, прописанном в системной переменной PATH или в том же каталоге, что и исполняемый файл, иначе вы получите сообщение об ошибке. Однако если 10 программ используют эту DLL, вам нужна всего одна ее копия, лежащая, например, в каталоге Windows\System.

Результаты работы DLLRUN01.EXE

This function was called from C:\DLLTEST\DLLRUN01.EXE
NumberList(): 0 1 2 3 4 5 6 7 8 9

This function was called from C:\DLLTEST\DLLRUN01.EXE
LetterList(): a b c d e f g h i j k l m n o p q r s t u v w x y z

Использование DLL без библиотеки импорта

Теперь посмотрим, как загрузить DLL "на лету". Это нужно в случае, если не вы разрабатывали эту DLL и у вас нет заголовочного файла и библиотеки импорта.

Исходный файл приложения, использующего библиотеку DLL - консольное приложение Win32 (DLLRUN01.EXE)

#include <windows.h>
#include <iostream.h>
#include <stdio.h>
#include <conio.h>

#define MAXMODULE 50

typedef void (WINAPI*cfunc)();

cfunc NumberList;
cfunc LetterList;

void main()
{
   HINSTANCE hLib=LoadLibrary("DLLTEST.DLL");
    if(hLib==NULL)
    {
       cout << "Unable to load library!" << endl;
       getch();
       return;
    }

   char mod[MAXMODULE];

   GetModuleFileName((HMODULE)hLib, (LPTSTR)mod, MAXMODULE);
   cout << "Library loaded: " << mod << endl;
   NumberList=(cfunc)GetProcAddress((HMODULE)hLib, "NumberList");
   LetterList=(cfunc)GetProcAddress((HMODULE)hLib, "LetterList");
    if((NumberList==NULL) || (LetterList==NULL))
    {
       cout << "Unable to load function(s)." << endl;
       FreeLibrary((HMODULE)hLib);
       return;
    }
    NumberList();
    LetterList();
    FreeLibrary((HMODULE)hLib);
    getch();
}

Этот код загружает DLL (если она находится в путях или в текущем каталоге), а затем определяет адреса функций, которые мы будем вызывать. Конечно, в этом случае пришлось написать намного больше кода, и, соответственно, придется отловить немало ошибок. Однако такой подход универсальней.

Результаты работы DLLRUN02.EXE

Library loaded: C:\DLLTEST\DLLTEST.DLL

This function was called from C:\DLLTEST\DLLRUN02.EXE
NumberList(): 0 1 2 3 4 5 6 7 8 9

This function was called from C:\DLLTEST\DLLRUN02.EXE
LetterList(): a b c d e f g h i j k l m n o p q r s t u v w x y z

Данная страница с сайта - http://www.xserver.ru


Динамические библиотеки для начинающих

Динамические библиотеки для начинающих

(статья была опубликована в журнале "Программист")

В наше время Windows-разработчик шагу не может ступить без динамических библиотек(Dynamic Link Library - DLL); а перед начинающими программистами, желающими разобраться в предмете, встает масса вопросов:

    1. как эффективно использовать чужие DLL?
    2. как создать свою собственную?
    3. какие способы загрузки DLL существуют, и чем они отличаются?
    4. как загружать ресурсы из DLL?

Обо всем этом (и многом другом) рассказывает настоящая глава. Материал рассчитан на пользователейMicrosoft Visual C++, а поклонникам других языков и компиляторов придется разбираться с ключами компиляции приведенных примеров самостоятельно.

Создание собственной DLL

С точки зрения программиста - DLL представляет собой библиотеку функций (ресурсов), которыми может пользоваться любой процесс, загрузивший эту библиотеку. Сама загрузка, кстати, отнимает время и увеличивает расход потребляемой приложением памяти; поэтому бездумное дробление одного приложения на множество DLL ничего хорошего не принесет.

Другое дело - если какие-то функции используются несколькими приложениями. Тогда, поместив их в одну DLL, мы избавимся от дублирования кода и сократим общий объем приложений - и на диске, и в оперативной памяти. Можно выносить в DLL и редко используемые функции отдельного приложения; например, немногие пользователи текстового редактора используют в документах формулы и диаграммы - так зачем же соответствующим функциям впустую "отъедать" память?

Загрузившему DLL процессу доступны не все ее функции, а лишь явно предоставляемые самой DLL для "внешнего мира" - т. н. экспортируемые. Функции, предназначенные сугубо для "внутреннего" пользования, экспортировать бессмысленно (хотя и не запрещено). Чем больше функций экспортирует DLL - тем медленнее она загружается; поэтому к проектированию интерфейса (способа взаимодействия DLL с вызывающим кодом) следует отнестись повнимательнее. Хороший интерфейс интуитивно понятен программисту, немногословен и элегантен: как говорится, ни добавить, ни отнять. Строгих рекомендаций на этот счет дать невозможно - умение приходит с опытом

Для экспортирования функции из DLL - перед ее описанием следует указать ключевое слово __declspec(dllexport), как показано в следующем примере:

// myfirstdll.c
#include <stdio.h>

// Ключевое слово __declspec(dllexport)
// делает функцию экспортируемой
__declspec(dllexport) void Demo(char *str)
{
  // Выводим на экран переданную функции Demo строку
  printf(str);
}

Листинг 10 Демонстрация экспорта функции из DLL

Для компиляции этого примера в режиме командной строки можно запустить компилятор Microsoft Visual Studio: "cl.exe myfirstdll.c /LD". Ключ "/LD" указывает линкеру, что требуется получить именно DLL.

Для сборки DLL из интегрированной оболочки Microsoft Visual Studio - при создании нового проекта нужно выбрать пункт "Win32 Dynamics Link Library", затем "An Empty DLL project"; потом перейти к закладке "File View" окна "Workspace" - и, выбрав правой клавишей мыши папку "Source Files", добавить в проект новый файл ("Add Files to Folder"). Компиляция осуществляется как обычно ("Build" ( "Build").

Если все прошло успешно - в текущей директории (или в директории Release\Debug при компиляции из оболочки) появится новый файл - "MyFirstDLL.dll". Давайте заглянем в него через "микроскоп" - утилиту dumpbin, входящую в штатную поставку SDK и Microsoft Visual Studio: "dumpbin /EXPORTS MyFirstDLL.dll". Ответ программы в несколько сокращенно виде должен выглядеть так:

  Section contains the following exports for myfirst.dll
             0 characteristics
          0.00 version
             1 ordinal base
             1 number of functions
             1 number of names
             1    0 00001000 Demo

Получилось! Созданная нами DLL действительно экспортирует функцию "Demo" - остается только разобраться, как ее вызывать

Вызов функций из DLL

Существует два способа загрузки DLL: с явной и неявной компоновкой.

При неявной компоновке функции загружаемой DLL добавляются в секцию импорта вызывающего файла. При запуске такого файла загрузчик операционной системы анализирует секцию импорта и подключает все указанные библиотеки. Ввиду своей простоты этот способ пользуется большой популярностью; но простота - простотой, а неявной компоновке присущи определенные недостатки и ограничения:

  1. все подключенные DLL загружаются всегда, даже если в течение всего сеанса работы программа ни разу не обратится ни к одной из них;
  2. если хотя бы одна из требуемых DLL отсутствует (или DLL не экспортирует хотя бы одной требуемой функции) - загрузка исполняемого файла прерывается сообщением "Dynamic link library could not be found" (или что-то в этом роде) - даже если отсутствие этой DLL некритично для исполнения программы. Например, текстовой редактор мог бы вполне работать и в минимальной комплектации - без модуля печати, вывода таблиц, графиков, формул и прочих второстепенных компонентов, но если эти DLL загружаются неявной компоновкой - хочешь не хочешь, придется "тянуть" их за собой.
  3. поиск DLL происходит в следующем порядке: в каталоге, содержащем вызывающий файл; в текущем каталоге процесса; в системном каталоге %Windows%System%; в основном каталоге %Windows%; в каталогах, указанных в переменной PATH. Задать другой путь поиска невозможно (вернее - возможно, но для этого потребуется вносить изменения в системный реестр, и эти изменения окажут влияние на все процессы, исполняющиеся в системе - что не есть хорошо).

Явная компоновка устраняет все эти недостатки - ценой некоторого усложнения кода. Программисту самому придется позаботиться о загрузке DLL и подключении экспортируемых функций (не забывая при этом о контроле над ошибками, иначе в один прекрасный момент дело кончится зависанием системы). Зато явная компоновка позволяет подгружать DLL по мере необходимости и дает программисту возможность самостоятельно обрабатывать ситуации с отсутствием DLL. Можно пойти и дальше - не задавать имя DLL в программе явно, а сканировать такой-то каталог на предмет наличия динамических библиотек и подключать все найденные к приложению. Именно так работает механизм поддержки plug-in’ов в популярном файл-менеджере FAR (да и не только в нем).

Таким образом, неявной компоновкой целесообразно пользоваться лишь для подключения загружаемых в каждом сеансе, жизненно необходимых для работы приложения динамических библиотек; во всех остальных случаях - предпочтительнее явная компоновка.

Загрузка DLL с неявной компоновкой

Чтобы вызвать функцию из DLL, ее необходимо объявить в вызывающем коде - либо как external (т. е. как обычную внешнюю функцию), либо предварить ключевым словом __declspec(dllimport). Первый способ более популярен, но второй все же предпочтительнее - в этом случае компилятор, поняв, что функция вызывается именно из DLL, сможет соответствующим образом оптимизировать код. Например, функция "Demo" из созданной нами библиотеки - "MyFirstDll" вызывается так:

  // ImplictDll.c
  // Объявляем внешнюю функцию Demo
  __declspec(dllimport) void Demo(char *str);

  main()
  {
    // Вызываем функцию Demo из DLL
    Demo("Hello, World!\n");
  }

Листинг 11 Демонстрация вызова функции из DLL неявной компоновкой

Из командной строки данный пример компилируется так: "cl.exe ImplictDll.c myfirstdll.lib", где "myfirstdll.lib" - имя библиотеки, автоматически сформированной компоновщиком при создании нашей DLL.

Разумеется, "чужие" DLL не всегда поставляются вместе с сопутствующими библиотеками, но их можно легко изготовить самостоятельно! На этот случай предусмотрена специальная утилита implib, поставляемая вместе с компилятором, и вызываемая так: "implib.exe Имя_файла _создаваемой_библиотеки Имя_DLL".

В нашем случае - не будь у нас файла "MyFirstDLL.lib", его пришлось бы получить так: "implib.exe MyFirstDLL.lib MyFirstDLL.dll". Со всеми стандартными DLL, входящими в состав Windows, эту операцию проделывать не нужно, т.к. необходимые библиотеки распространяются вместе с самим компилятором.

Для подключения библиотеки в интегрированной среде Microsoft Visual Studio - в меню "Project" выберите пункт "Project Settings", в открывшемся диалоговом окне перейдите к закладке "Link" и допишите имя библиотеки в конец строки "Object/Library Modules", отделив ее от остальных символом пробела.

Если все прошло успешно, появится новый файл "ImplictDll.exe", который, будучи запущенным, горделиво выведет на экран "Hello, Word!". Это означает, что наша DLL подключена и успешно работает.

Заглянем внутрь: как это происходит? Запустим "dumpbin /IMPORTS ImplictDll.exe" и посмотрим, что нам сообщит программа:

  File Type: EXECUTABLE IMAGE

    Section contains the following imports:

      myfirstdll.dll
             404090 Import Address Table
             4044C8 Import Name Table
                  0 time date stamp
                  0 Index of first forwarder reference

                0  Demo

      KERNEL32.dll
             404000 Import Address Table
             404438 Import Name Table
                  0 time date stamp
                  0 Index of first forwarder reference

              19B  HeapCreate
              2BF  VirtualFree
               CA  GetCommandLineA
              174  GetVersion
               7D  ExitProcess
              29E  TerminateProcess
               F7  GetCurrentProcess

Вот она - "Myfirstdll.dll" (в тексте выделена жирным шрифтом), и вот функция "Demo", а кроме нее - обнаруживает свое присутствие библиотека KERNEL32.DLL – она необходима RTL-коду (Run Time Library - библиотека времени исполнения), насильно помещенному компилятором в наше приложение. RTL-код обеспечивает работу с динамической памятью (heap), считывает аргументы командной строки, проверяет версию Windows и многое-многое другое! Отсюда и появляются в таблице импорта функции HeapCreate, GetCommandLine, GetVersion и т.д. Так что - не удивляйтесь, увидев "левый" импорт в своем приложении!

Проследить, как именно происходит загрузка DLL, можно с помощью отладчика. Общепризнанный лидер - это, конечно, SoftIce от NuMega, но для наших экспериментов вполне сойдет и штатный отладчик Microsoft Visual Studio. Откомпилировав нашу вызывающую программу, нажмем <F10> для пошагового прогона приложения

Оппаньки! Не успело еще выполниться ни строчки кода, как в окне "output" отладчика появились следующие строки, свидетельствующие о загрузке внешних DLL: NTDLL.DLL, MyFirstDll.dll и Kernel32.dll. Так и должно быть - при неявной компоновке динамические библиотеки подключаются сразу же при загрузке файла, задолго до выполнения функции main!

Loaded 'C:\WINNT\System32\ntdll.dll', no matching symbolic information found.
Loaded 'F:\ARTICLE\PRG\DLL.files\myfirstdll.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\kernel32.dll', no matching symbolic information found.

Загрузка DLL с явной компоновкой

Явную загрузку динамических библиотек осуществляет функция HINSTANCE LoadLibrary(LPCTSTR lpLibFileName) или ее расширенный аналог HINSTANCE LoadLibraryEx(LPCTSTR lpLibFileName, HANDLE hFile, DWORD dwFlags).

Обе они экспортируются из KERNEL32.DLL, следовательно, каждое приложение требует неявной компоновки по крайней мере этой библиотеки. В случае успешной загрузки DLL возвращается линейный адрес библиотеки в памяти. Передав его функции FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName) - мы получим указатель на функцию lpProcName, экспортируемую данной DLL. При возникновении ошибки обе функции возвращают NULL. После завершения работы с динамической библиотекой ее следует освободить вызовом функции BOOL FreeLibrary(HMODULE hLibModule). Для пояснения приведем код примера с подробными комментариями:

// DynCall.c
#include <stdio.h>
#include <windows.h>

main()
{
  // Дескриптор загружаемой dll
  HINSTANCE h;

  // Объявление указателя на функцию, вызываемой из DLL
  // Обратите внимание – имена объявляемой функции и
  // функции, вызываемой из DLL, могут и не совпадать,
  // т.к. за выбор вызываемой функции отвечает
  // GetProcAddress
  void (*DllFunc) (char *str);

  // Загружаем MyFirstDLL
  h=LoadLibrary("MyFirstDLL.dll");

  // Контроль ошибок – если загрузка прошла успешно,
  // функция вернет что-то отличное от нуля
  if (!h)
  {
    printf("Ошибка - не могу найти MyFirstDLL.dll\n");
    return;
  }

  // Вызовом GetProcAddress получаем адрес функции Demo
  // и присваиваем его указателю DllFunc с явным
  // приведением типов. Это необходимо т.к.
  // GetProcAddress возвращает бестиповой far-указатель
  DllFunc=(void (*) (char *str))
            GetProcAddress(h,"Demo");

  // Контроль ошибок – если вызов функции GetProcAddress
  // завершился успешно, она вернет ненулевой указатель
  if (!DllFunc)
  {
    printf("Ошибка! В MyFirstDLL "
           "отсутствует ф-ция   Demo\n");
    return;
  }

  // Вызов функции Demo из DLL
  DllFunc("Test");

  // Выгрузка динамической библиотеки из памяти
  FreeLibrary(h);
}

Листинг 12 Демонстрация вызова функции из DLL явной компоновкой

Компилировать так: "cl DynCall.c" - никаких дополнительных библиотек указывать не нужно (необходимые kernel32.lib и LIBC.lib компоновщик подключит самостоятельно).

В интегрированной среде Microsoft Visual Studio достаточно щелкнуть мышкой по иконке "Build" - и никаких дополнительных настроек!

Для изучения секции импорта только что полученного файла запустим утилиту Dumpbin – обратите внимание, что здесь отсутствует всякое упоминание о MyFirstDLL.dll, но обнаруживаются две функции: LoadLibrary и GetProcAddress - которые и загружают нашу библиотеку. Это очень важное обстоятельство - изучение секции импорта исследуемого файла не всегда позволяет установить полный перечень функций и динамических библиотек, которые использует приложение. Наличие LoadLibrary и GetProcAddress красноречиво свидетельствует о том, что приложение подгружает какие-то модули во время работы самостоятельно.

Чтобы выяснить какие - запустим его под отладчиком. Сразу же после загрузки исполняемого файла в окне Output появятся строки:

Loaded 'C:\WINNT\System32\ntdll.dll', no matching symbolic information found.
Loaded 'C:\WINNT\system32\kernel32.dll', no matching symbolic information found.

Это опять грузятся обязательные KERNEL32.DLL и NTDLL.DLL (последнее - только под Windows NT/2000). Никакого упоминания о MyFirstDLL.dll еще нет. Пошагово исполняя программу ("Debug" ( "Step Over"), дождемся выполнения функции LoadLibrary. Тут же в Output-окне появится следующая строка:

Loaded 'F:\ARTICLE\PRG\DLL.files\myfirstdll.dll', no matching symbolic information found.

Наша динамическая библиотека загрузилась; но не сразу после запуска файла (как это происходило при неявной компоновке), а только когда в ней возникла необходимость!

Если же по каким-то причинам DLL не найдется, или окажется, что в ней отсутствует функция Demo - операционная система не станет "убивать" приложение с "некрологом" критической ошибки, а предоставит программисту возможность действовать самостоятельно. В качестве эксперимента попробуйте удалить (переименовать) MyFirstDLL.dll и посмотрите, что из этого получится.

Выгрузка динамических библиотек из памяти

Когда загруженная динамическая библиотека больше не нужна - ее можно освободить, вызвав функцию BOOL FreeLibrary(HMODULE hLibModule) и передав ей дескриптор библиотеки, ранее возвращенный функцией LoadLibrary. Обратите внимание - DLL можно именно освободить, но не выгрузить! Выгрузка DLL из памяти не гарантируется, даже если работу с ней завершили все ранее загрузившие ее процессы.

Задержка выгрузки предусмотрена специально - на тот случай, если эта же DLL через некоторое время вновь понадобится какому-то процессу. Такой трюк оптимизирует работу часто используемых динамических библиотек, но плохо подходит для редко используемых DLL, загружаемых лишь однажды на короткое время. Никаких документированных способов насильно выгрузить динамическую библиотеку из памяти нет; а те, что есть - работают с ядром на низком уровне и не могут похвастаться переносимостью. Поэтому здесь мы их рассматривать не будем. К тому же - тактика освобождения и выгрузки DLL по-разному реализована в каждой версии Windows: Microsoft, стремясь подобрать наилучшую стратегию, непрерывно изменяет этот алгоритм; а потому и отказывается его документировать.

Нельзя не обратить внимания на одно очень важное обстоятельство: динамическая библиотека не владеет никакими ресурсами - ими владеет, независимо от способа компоновки, загрузивший ее процесс. Динамическая библиотека может открывать файлы, выделять память и т. д., но память не будет автоматически освобождена после вызова FreeLibrary, а файлы не окажутся сами собой закрыты - все это произойдет лишь после завершения процесса, но не раньше! Естественно, если программист сам не освободит все ненужные ресурсы вручную, с помощью функций CloseHandle, FreeMemory и подобных им.

Если функция FreeLibrary пропущена, DLL освобождается (но не факт, что выгружается!) только после завершения вызвавшего процесса. Могут возникнуть сомнения: раз FreeLibrary немедленно не выгружает динамическую библиотеку из памяти, так зачем она вообще нужна? Не лучше ли тогда все пустить на самотек - все равно ведь загруженные DLL будут гарантированно освобождены после завершения процесса? Что ж, доля правды тут есть, и автор сам порой так и поступает; но при недостатке памяти операционная система может беспрепятственно использовать место, занятое освобожденными динамическими библиотеками под что-то полезное - а если DLL еще не освобождены, их придется "скидывать" в файл подкачки, теряя драгоценное время. Поэтому лучше освобождайте DLL сразу же после их использования!

ООП и DLL

Динамические библиотеки ничего не знают ни о каком ООП! Они не имеют ни малейшего представления о существовании классов! Все, что умеют DLL - экспортировать одно или несколько имен функций (ресурсов, глобальных переменных), а уж как его использовать - решать программисту или компилятору.

Что ж; испытаем компилятор на "сообразительность", включив в описание класса ключевое слово __declspec(dllexport) – и посмотрим, что из этого выйдет:

  // DLLclass.cpp
  #include <stdio.h>

  class __declspec(dllexport) MyDllClass{
  public:
    Demo(char *str);
  };

  MyDllClass::Demo(char *str)
  {
    printf(str);
  }

Листинг 13 Демонстрация экспорта класса из DLL

Откомпилируем этот код как обычную DLL и заглянем в таблицу импорта утилитой dumpbin:

dumpbin /EXPORTS DLLclass.dll

File Type: DLL
 Section contains the following exports for DLLclass.dll
          0 characteristics
   3B1B98E6 time date stamp Mon Jun 04 18:19:18 2001
       0.00 version
          1 ordinal base
          2 number of functions
          2 number of names
   ordinal hint RVA      name

         1    0 00001000 ??4MyDllClass@@QAEAAV0@ABV0@@Z
         2    1 00001020 ?Demo@MyDllClass@@QAEHPAD@Z

Таблица импорта явно не пуста - но выглядит странно. Компилятор искалечил имена, чтобы втиснуть в них информацию о классах и аргументах функций без нарушений жестких ограничений, налагаемых стандартом на символы, допустимые в экспортируемых именах (например, запрещается использовать знак двоеточие, скобка и т. д.).

Как же со всем этим работать? Попробуй-ка угадай - во что превратится то или иное имя после компиляции! Впрочем, при неявной компоновке ни о чем гадать не придется, т. к. обо всем позаботится сам компилятор, а от программиста потребуется лишь описать класс, предварив его ключевым словом __declspec(dllimport):

  // DLLClassCall.cpp
  #include <stdio.h>

  class __declspec(dllimport) MyDllClass{
  public:
    Demo(char *str);
  };

  main()
  {
    MyDllClass zzz;
    zzz.Demo("Привет, ООП! А мы тут!");
  }

Листинг 14 Демонстрация импорта класса из DLL неявной компоновкой

Откомпилируйте пример как обычную программу с неявной компоновкой ("cl DLLClassCall.cpp DLLClass.lib") и попробуйте запустить полученный файл. Работает? Никакой разницы с "классическим" Си нет, не правда ли? Вот только как подключить DLL с явной компоновкой? Неужели нельзя запретить компилятору "калечить" имена функций?! Конечно же, можно

Мангл и как его побороть или импорт классов из DLL явной компоновкой

Искажение функций Cи ++ компилятором называется по-английски "mangle", и среди русскоязычных программистов широко распространена его калька - "манглить". В принципе "манглеж" функций не препятствует их явному вызову посредством GetProcAddress - достаточно лишь знать, как точно называется та или иная функция, что нетрудно выяснить тем же dumpbin. Однако засорение программы подобными "заученными" именами выглядит не очень-то красиво; к тому же - всякий компилятор "манглит" имена по-своему, и непосредственное использование имен приводит к непереносимости программы.

Существует способ обойти искажение имен - для этого необходимо подключить к линкеру специальный DEF-файл, перечисляющий имена, которые не должны измениться. В нашем случае он должен выглядеть так

  // DllClass.def:
  EXPORTS
    Demo

Листинг 15 Отказ от "замангления" имен

Сперва идет ключевое слово "EXPORTS", за которым следуют одно или несколько "неприкасаемых" имен. Каждое имя начинается с новой строки, и в его конце не указывается точка с запятой.

Для подключения DEF-файла при компиляции из командной строки - используйте опцию "/link /DEF:имя_файла.def", например так: "cl DLLclass.cpp /LD /link /DEF:DLLClass.def".

Для подключения DEF-файла в интегрированной среде Microsoft Visual Studio - перейдите к закладке "File View" окна "Workspace" и, щелкнув правой клавишей по папке "Source Files", выберите в контекстом меню пункт "Add Files to Folder", а затем укажите путь к DEF-файлу. Откомпилируйте проект как обычно: "Build" ( "Build".

Заглянув в таблицу импорта полученного DLL-файла, мы, среди прочей информации, увидим следующее:

  1    0 00001000 ??4MyDllClass@@QAEAAV0@ABV0@@Z
  2    1 00001020 Demo

Теперь имя функции Demo выглядит "как положено". А абракадабра, расположенная строчкой выше - это конструктор класса MyDllClass, который, хоть и не был специально объявлен, все равно экспортируется из динамической библиотеки.

Однако, избавившись от одной проблемы, мы получаем другую - имя функции Demo потеряло всякое представление о классе, которому оно принадлежало; и теперь придется загружать его вручную, повторяя эту операцию для каждого элемента класса. Фактически - придется в вызываемой программе собирать "скелет" класса из "косточек" заново. Но иного способа явной загрузки класса из DLL не существует.

Следующий пример демонстрирует вызов функции MyDllClass из динамической библиотеки с явной компоновкой. Обработка ошибок для упрощения опущена. Обратите внимание, как объявляется функция Demo - описания класса в DLL и в вызывающей программе существенно отличаются, поэтому с идеей поместить описания класса в общий для всех include-файл придется расстаться.

  // DeMangle.cpp
  #include <windows.h>

  class MyDllClass{
  public:
    void (*Demo) (char *str);
  };

  main()
  {
    HINSTANCE h=LoadLibrary("DllClass.dll");
    MyDllClass zzz;

    // Внимание! Выполнение конструктора / деструктора
    // класса при явной загрузке не происходит
    // автоматически и при необходимости эту операцию
    // следует выполнить вручную
    zzz.Demo=(void (*) (char *str))
               GetProcAddress(h,"Demo");
    zzz.Demo("Test");
  }

Листинг 16 Демонстрация вызова функции MyDllCLass::Demo явной компоновкой

Загрузка ресурсов из DLL

Помимо функций, динамические библиотеки могут содержать и ресурсы - строки, иконки, рисунки, диалоги и т. д. Хранение ресурсов в DLL очень удобно; в частности - при создании приложений с многоязычным интерфейсом: заменив одну DLL на другую, мы заменяем все надписи в программе, скажем, с английского на русский - и, заметьте, без всяких "хирургических вмешательств" в код приложения! Аналогично можно менять иконки, внешний вид диалогов и т. д.

Создание DLL, содержащей ресурсы, ничем не отличается от создания исполняемого приложения с ресурсами; сначала необходимо создать сам файл ресурсов - например, так:

  // MyResDll.rc
  #pragma code_page(1251)

  STRINGTABLE DISCARDABLE
  BEGIN
      1             "Hello, World!"
  END

Листинг 17 Создание DLL, содержащей одни лишь ресурсы

Файл ресурсов надо скомпилировать - "rc MyResDll.rc" - и преобразовать линкером в DLL, обязательно указав флаг "/NOENTRY", т. к. эта динамическая библиотека содержит исключительно одни ресурсы и ни строки кода: "link MyRedDll.res /DLL /NOENTRY".

В Visual Studio это сделать еще проще - достаточно кликнуть по папке "Resourses" окна "File View" и добавить новый файл ресурса, который затем можно будет модифицировать визуальным редактором по своему усмотрению.

Для загрузки ресурса из DLL - в принципе, можно воспользоваться уже знакомой нам функцией LoadLibray, и передавать возращенный ею дескриптор LoadString или другой функции, работающей с ресурсами. Однако загрузку динамической библиотеки можно значительно ускорить, если "объяснить" системе, что эта DLL не содержит ничего, кроме ресурсов, и нам достаточно лишь спроецировать ее на адресное пространство процесса, а обо всем остальном мы сумеем позаботиться и самостоятельно.

Вот тут-то и пригодится функция LoadLibraryEx: ее первый аргумент, как и у коллеги LoadLibrary, задает имя динамической библиотеки для загрузки, второй - зарезервирован и должен быть равен нулю, а третий, будучи равным LOAD_LIBRARY_AS_DATAFILE, заставляет функцию делать именно то, что нам нужно - загружать DLL как базу данных (если динамическая библиотека содержит помимо ресурсов еще и код, то загрузка с этим ключом проходит все равно успешно, но функции загруженной DLL не будут доступны - только ресурсы):

  // DllLoadRes.c
  #include <stdio.h>
  #include <windows.h>

  main()
  {
    HINSTANCE h;
    char buf[100];
    h=LoadLibraryEx("MyResDll.dll",0,
        LOAD_LIBRARY_AS_DATAFILE);

    LoadString(h,1,&buf[0],99);
    printf("%s\n",&buf[0]);
  }

Листинг 18 Демонстрация оптимизированной загрузки DLL, не содержащей ничего кроме ресурсов

Эта программа компилируются точно так же, как и предыдущие примеры явной компоновки - и после запуска победно выводит на экране "Hello, Word!", подтверждая, что ресурс "строка" из динамической библиотеки был успешно загружен! Аналогичным способом можно загружать ресурсы из исполняемых файлов; с этой точки зрения они ничем не отличаются от динамических библиотек.


Создание и использование динамических библиотек, написанных на различных языках (C/C++, Pascal)

Создание и использование динамических библиотек, написанных на различных языках (C/C++, Pascal)

Задача

Передо мной возникла задача написать загрузчик библиотек, имеющий возможность предоставить какие-то интерфейсные функции внешней динамической библиотеке. Решение должно быть максимально кроссплатформенно (как минимум, работать на Linux и Windows). Загружаться должны библиотеки, написанные на различных языках программирования, поддерживающих создание динамических библиотек. В качестве примера были выбраны языки C и Pascal.

Решение

Основной загрузчик библиотек написан на языке C. Для того, чтобы загружаемые библиотеки имели возможность использовать функции основной программы, основная программа разделена на 2 части: на основной и подгружаемый модули. Основной модуль нужен просто для запуска программы, подгружаемый модуль — это также динамическая библиотека, связываемая с основным модулем во время его запуска. В качестве компиляторов были выбраны gcc (MinGW для Windows) и fpc.
Здесь будет приведён упрощённый пример программы, позволяющий разобраться в данном вопросе и учить первокурсников писать модули к своей программе (в школе часто преподают именно Pascal).

Загрузчик библиотек

Главный файл загрузчика библиотек выглядит очень просто:

main.c

#include "loader.h"

#ifdef __cplusplus
extern "C" {
#endif

int main(int argc, char *argv[]) {
  if (argc > 1) {
    loadRun(argv[1]);
  }
  return 0;
}

#ifdef __cplusplus
}
#endif

А это модуль, отвечающий за загрузку динамических библиотек, который сам вынесен в динамическую библиотеку для того, чтобы подгружаемые библиотеки имели возможность использовать предоставляемые им функции:

loader.c

#include "loader.h"
#include "functions.h"
#include <stdio.h>

#ifndef WIN32
#include <dlfcn.h>
#else
#include <windows.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

void printString(const char * const s) {
  printf("String from library: %s\n", s);
}

void loadRun(const char * const s) {
   void * lib;
   void (*fun)(void);
#ifndef WIN32
   lib = dlopen(s, RTLD_LAZY);
#else
   lib = LoadLibrary(s);
#endif
   if (!lib) {
     printf("cannot open library '%s'\n", s);
     return;
   }
#ifndef WIN32
   fun = (void (*)(void))dlsym(lib, "run");
#else
   fun = (void (*)(void))GetProcAddress((HINSTANCE)lib, "run");
#endif
   if (fun == NULL) {
     printf("cannot load function run\n");
   } else {
     fun();
   }
#ifndef WIN32
   dlclose(lib);
#else
   FreeLibrary((HINSTANCE)lib);
#endif
}

#ifdef __cplusplus
}
#endif

Заголовочные файлы

Это всё была реализация, а теперь заголовочные файлы.
Вот интерфейс модуля, загружающего динамические библиотеки, для основного модуля:

loader.h

#ifndef LOADER_H
#define LOADER_H

#ifdef __cplusplus
extern "C" {
#endif

extern void loadRun(const char * const s);

#ifdef __cplusplus
}
#endif

#endif

Вот интерфейс загрузчика для загружаемых им динамических библиотек (перечень функций, которые динамические библиотеки могут использовать в основной программе):

functions.h

#ifndef FUNCTIONS_H
#define FUNCTIONS_H

#ifdef __cplusplus
extern "C" {
#endif

extern void printString(const char * const s);

#ifdef __cplusplus
}
#endif

#endif

Как видно, здесь всего одна функция printString для примера.

Компиляция загрузчика

Пример недистрибутивной компиляции (в случае Windows в опции компилятора просто нужно добавить -DWIN32):

$ gcc -Wall -c main.c
$ gcc -Wall -fPIC -c loader.c
$ gcc -shared -o libloader.so loader.o -ldl
$ gcc main.o -ldl -o run -L. -lloader -Wl,-rpath,.

Дистрибутивная компиляция от недистрибутивной отличается тем, что в дистрибутивном случае динамические библиотеки ищутся в /usr/lib и имеют вид lib$(NAME).so.$(VERSION), в случае недистрибутивной компиляции они называются lib$(NAME).so, а ищутся в каталоге запуска программы.

Теперь посмотрим, что у нас получилось после компиляции:

$ nm run | tail -n 2
         U loadRun
08048504 T main
$ nm libloader.so| tail -n 4
000005da T loadRun
000005ac T printString
         U printf@@GLIBC_2.0
         U puts@@GLIBC_2.0

Тут видим, что функции, отмечаемые как U ищутся во внешних динамических библиотеках, а функции, отмечаемые как T предоставляются модулем. Это бинарный интерфейс программы (ABI).

Динамические библиотеки

Теперь приступим к описанию самих динамических библиотек.

Библиотека на языке C

Вот пример простейшей библиотеки на языке C:

lib.c

#include "functions.h"

#ifdef __cplusplus
extern "C" {
#endif

void run(void) {
  printString("Hello, world!");
}

#ifdef __cplusplus
}
#endif

Здесь везде окружение extern «C» {} нужно для того, чтобы нашу программу можно было компилировать при помощи C++-компилятора, такого, как g++. Просто в C++ можно объявлять функции с одним и тем же именем, но с разной сигнатурой, соответственно в этом случае используется так называемая декорация имён функций, то есть сигнатура функций записывается в ABI. Окружение extern «C» {} нужно для того, чтобы не использовалась эта декорация (тем более, что эта декорация зависит от используемого компилятора).

Компиляция

$ gcc -Wall -fPIC -c lib.c
$ gcc -shared -o lib.so lib.o

ABI:

$ nm lib.so | tail -n 2
         U printString
0000043c T run

Запуск:

$ ./run lib.so
String from library: Hello, world!

Если мы уберём в нашем модуле окружение extern «C» {} и скомпилируем при помощи g++ вместо gcc, то увидим следующее:

$ nm lib.so | grep run
0000045c T _Z3runv

То есть, как и ожидалось, ABI библиотеки изменился, теперь наш загрузчик не сможет увидеть функцию run в этой библиотеке:

$ ./run lib.so
cannot load function run

Библиотека на языке Pascal

Как мы увидели выше, для того, чтобы наш загрузчик видел функции в динамических библиотеках, созданных компилятором C++, пришлось дополнять наш код вставками extern «C» {}, это притом, что C/C++-компиляторы и языки родственные. Что уж говорить про компилятор FreePascal совершенно другого языка — Pascal? Естественно, что и тут без дополнительных телодвижений не обойтись.

Для начала нам нужно научиться использовать экспортированные в C функции для динамических библиотек. Вот пример аналогичного C/C++ заголовочного файла на языке Pascal:

func.pas

unit func;

interface
  procedure printString(const s:string); stdcall; external name 'printString';
implementation
end.

Вот пример самого модуля на языке Pascal:

modul.pas

library modul;
uses
  func;

procedure run; stdcall;
begin
  printString('Hello from module!');
end;

exports
  run;

begin
end.

Компиляция

$ fpc -Cg modul.pas
Компилятор Free Pascal версии 2.5.1 [2011/02/21] для i386
Copyright (c) 1993-2010 by Florian Klaempfl
Целевая ОС: Linux for i386
Компиляция modul.pas
Компоновка libmodul.so
/usr/bin/ld: warning: link.res contains output sections; did you forget -T?
/usr/bin/ld: warning: creating a DT_TEXTREL in a shared object.
13 строк скомпилиpовано, 6.6 сек

Смотрим ABI получившейся библиотеки:

$ nm libmodul.so
         U printString
000050c0 T run

Как видим, ничего лишнего, однако настораживает предупреждение ld во время компиляции. Находим в гугле возможную причину предупреждения , это связано с компиляцией без PIC (Position Independent Code — код не привязан к физическому адресу), однако в man fpc находим, что наша опция -Cg должна генерировать PIC-код, что само по себе странно, видимо fpc не выполняет своих обещаний, либо я делаю что-то не так.

Теперь попробуем убрать в нашем заголовочном файле кусок name 'printString' и посмотрим, что выдаст компилятор теперь:

$ nm libmodul.so
         U FUNC_PRINTSTRING$SHORTSTRING
000050d0 T run

Как видно, декорация в FreePascal совсем другого рода, чем в g++ и тоже присутствует.
При запуске с этим модулем получаем:

$ ./run libmodul.so
./run: symbol lookup error: ./libmodul.so: undefined symbol: FUNC_PRINTSTRING$SHORTSTRING

А с правильным модулем получаем:

$ ./run libmodul.so
String from library: Hello from module!

Вот и всё, своей задачи — использование динамических библиотек, написанных на различных языках — мы достигли, и наш код работает как под Linux, так и под Windows (у самого Mac'а нет, поэтому не смотрел, как там с этим). Кроме того, загруженные библиотеки имеют возможность использовать предоставляемые в основной программе функции для взаимодействия с ней (то есть являются плагинами основной программы).

Сам загрузчик может выполняться в отдельном процессе, чтобы эти плагины не смогли сделать ничего лишнего с основной программой. Соответственно, основная программа может быть написана на любом другом удобном языке программирования (например, на Java или на том же C++).


Создание динамически загружаемой DLL библиотеки на C#

Создание динамически загружаемой DLL библиотеки на C#

Задача

Не так давно у нас появилась необходимость создать расширение для одной программы на языке C# с использованием WPF. Расширение представляет собой динамически загружаемую dll библиотеку, о создании которой и пойдет речь в данной статье.

Нам не удалось найти стандартное средство в языке C# для создание динамической dll, по этому были рассмотрены обходные методы:

Первый способ – это создание динамически загружаемой dll на managed C++, из которой, производилось обращение к сборке на C#

Второй способ – создание сборки на языке C# и путем декомпиляции в IL код и обратно, приведение этой библиотеки к нужному виду.

О втором способе расскажу подробнее:

Создание динамической dll путем пере компиляции в il

Для реализации нам потребуются две утилиты ilasm и ildasm, эти утилиты должны поставляться вместе со средой разработки, у меня они лежали в этих папках:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\

C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools

Для удобства я добавил эти пути в Path, потому что придется часто пользоваться этими утилитами в командной строке.

Далее необходимо создать библиотеку классов на языке C#. Функции которые необходимо экспортировать, должны быть публичными и статическими, и находиться в публичном классе. В качестве параметров и возвращаемого значения можно использовать простые типы, такие как string, int.

Пример кода:
public
class TestExportClass
{
public static void
Run()
{
MessageBox.Show("Привет из C#");
}
}

Скомпилируем проект, получаем dll библиотеку. (у меня TestDll.dll)

Теперь необходимо преобразовать dll библиотеку в il код. Для этого нужно запустить ildasm в командной строке со следующими параметрами

ildasm TestDll.dll /OUT:TestDll.il

Далее необходимо проделать некоторые манипуляции с получившимся il кодом.

Во-первых, меняем флаг

.corflags 0?00000001

на

.corflags 0?00000002

Во-вторых, над началом описания классов (обычно эта область отделена комментарием CLASS MEMBERS DECLARATION) добавляем код:

.vtfixup [1] int32 fromunmanaged at VT_01

.data VT_01 = int32(0)

В-третьих, в методе который необходимо сделать экспортируемым добавляем код:

.vtentry 1 : 1

.export [1] as Run

Здесь Run – это название метода, по которому его будет вызывать сборка, написанная на неуправляемом коде.

Теперь необходимо скомпилировать dll библиотку, для этого воспользуемся утилитой ilasm, с параметрами:

ilasm TestDll.il /OUT:TestDll.dll /DLL

Все после этого dll можно использовать как обычную динамически загружаемую dll библиотеку.

Динамическая dll + WPF

Если вы планируете создавать расширение с использованием технологий, требующих, чтобы приложение работало в STA потоке (Например WPF), то необходимо учесть, что поток в котором будет выполняться расширение наследуется от сборки запустившей библиотеку, а значит может быть не в режиме STA.

Для того чтобы обойти эту проблему было решено в расширении создать STA поток и в нем запускать окна на WPF.

Вот так можно модернизировать код для запуска WPF окна:

public
static void Run()
{
Thread WPFThread = new
Thread(OpenWTPWindow);
WPFThread.ApartmentState =
ApartmentState.STA;
WPFThread.Start();
WPFThread.Join();
}

public
static void OpenWTPWindow()
{
System.Windows.Window WPFWindow = new
System.Windows.Window();
WPFWindow.ShowDialog();
}

Обратите внимание что окно запускается не с помощью функции Show() а ShowDialog(), функция Show() работает некорректно, при использовании её не в основном потоке.

Заключение

Такой метод зарекомендовал себя при разработки расширений для различных программ которые требуют, в качестве подключаемого модуля динамически загружаемую DLL библиотеку.

Справедливости ради стоит заметить, что после завершения вызванной процедуры, подгруженная dll библиотека продолжает находиться в памяти, что вызывает некоторые проблемы:
Например, когда мы создавали расширение для сервиса который постоянно запущен в системе, чтобы установить пользователю обновленное расширение, приходилось временно приостанавливать сервис

Данная статься с сайта habr.com


Создание библиотеки динамической компоновки Dll

Создание библиотеки динамической компоновки Dll

Иногда при запуске какой-либо программы появляется сообщение, что не найден файл *.dll. Для операционных систем Microsoft Windows, большая часть функциональных возможностей операционной системы обеспечивается библиотеками динамической компоновки (DLL). Кроме того, некоторые возможности программ могут быть реализованы в библиотеках DLL. Например некоторые программы могут содержать много различных модулей и при работе использовать только часть из них. Таким образом операционная система и программы загружаются быстрее, работают быстрее и занимают меньше места на диске компьютера.
Что такое DLL?
DLL — это библиотека, содержащая код и данные, которые могут использоваться несколько программами одновременно. Например, в операционных системах Windows, библиотека Comdlg32.dllвыполняет общие функции, связанные с диалоговыми окнами. Таким образом каждая программа может использовать функцию, которая содержится в этой библиотеке для реализации диалогового окна Открыть . Это позволяет повысить уровень повторного использования кода и эффективного использования памяти.
С помощью библиотек можно реализовать модульность для программы, в виде отдельных компонентов. Например бухгалтерскую программу можно продать по модулям. Каждый модуль может быть загружен в основной программе во время выполнения установки. Отдельные модули загружается только при запросе функций заложенных в них, поэтому загрузка программы выполняется быстрее.
Кроме того обновления легче применить для каждого модуля, не влияя на другие части программы. Например имеется программа по зарплате и надо изменить налоговые ставки за каждый год. Когда эти изменения изолированы в библиотеке, можно применить обновления без необходимости построения или установки программы целиком. Давайте рассмотрим пример создания библиотеки с самыми простыми математическими методами, такие как произведение, деление, сумма и разность.
Для начала, создадим новый проект, для этого запустите Microsoft Visual Studio и перейдите в меню Файл -> Создать -> Проект… или выполните сочетание клавиш Ctrl+Shift+N.
В открывшемся диалоговом окне выберете версию Framework, а в левой части «Установленные шаблоны» выберете «Visual C#», в центральной части вам будет представлен список шаблонов, выберете «Библиотека классов» и введите имя библиотеки, можно оставить по умолчанию.
У вас откроется вкладка с классом по умолчанию.
По умолчанию создается класс Class1, переименуем его в класс Calculator. Для этого перейдите в меню Вид - > Классы или выполните сочетание клавиш Ctrl + W, с последующем нажатием клавиши C.

В правой части программы у вас откроется вкладка «Окно классов».
Выберете по умолчанию созданный класс Class1, сделайте клик правой клавишей мыши по нему и выберете «Переименовать…».

В открывшемся окне введите новое имя класса Calculator и нажмите кнопку OK, обратите внимание, что данное окно позволяет переименовать класс во всем проекте.

После нажатия клавиши OK, вам будет предложено просмотреть список изменений, которые будут внесены в проект, а так же просмотр изменений кода.

Добавим в класс Calculator несколько методов и добавим к ним описание.

namespace ClassLibrary1
{
    ///
    /// Математический класс
    ///
    public class Calculator
    {
        ///
        /// Метод возвращает сумму двух целых чисел
        ///
        /// /// ///
        public static int Summ(int firstNumber, int secondNumber)
        {
            return firstNumber + secondNumber;
        }
        ///
        /// Метод возвращает разность двух целых чисел
        ///
        /// /// ///
        public static int Division(int firstNumber, int secondNumber)
        {
            return firstNumber - secondNumber;
        }
        ///
        /// Метод возвращает произведение двух чисел
        ///
        /// /// ///
        public static long Multiply(long x, long y)
        {
            return (x * y);
        }
        ///
        /// Метод возвращает деление двух чисел
        ///
        /// /// ///
        public static int Residual(int firstNumber, int secondNumber)
        {
            return (firstNumber / secondNumber);
        }
    }
}
По умолчанию для всех проектов стоит режим построения Debug(режим отладки), переведем проект в режим построения конечной версии (Release). Для этого перейдите в обозреватель решений и сделав клик правой клавишей мыши по названию проекта, выберете в открывшемся контекстном меню пункт «Свойства».
В открывшемся окне «Страницы свойств Решение “ClassLibrary1”» выберете везде конфигурацию Release, как показано на скриншоте ниже.
Наверно все замечали, что при наборе кода, появляется подсказка к методам или функциям. Мы задали такую подсказку в тегах. Но если сейчас просто выполнить построение библиотеки, то при подключении к другим проектам никаких подсказок видно не будет. Что бы устранить данную проблему, нам необходимо сформировать XML файл документации к проекту. Для этого в обозревателе решений выполните клик правой клавишей мыши по названию библиотеки и в открывшемся контекстном меню выберете пункт «Свойства».
После этих действий у вас откроется новая вкладка со свойствами проекта ClassLibrary1. Выберете в вкладку «Построение» и найдите раздел «Вывод», там вам будет предложено ввести путь куда будет выполнено построение конечной версии библиотеки и пункт необходимый нам для построения xml файла документации, тут вам необходимо просто поставить галочку как показано на скриншоте ниже. Тут важно чтобы библиотека и файл документации находились в одном месте, поэтому проверьте чтобы их путь вывода совпадал.

Остались последние шаги и мы получим готовую для использования библиотеку. И так продолжим, вам необходимо в верхней части программы выбрать режим конфигурации Release как показано на скриншоте ниже.

После этого, выполним построение решения. Нажав на клавиатуре клавишу F6.

Как только программа закончит построение, можно перейти в директорию с проектом и посмотреть что получилось. На этом этапе закончилось создание библиотеки.

Для проверки работоспособности библиотеки создадим тестовый проект. Выполните Файл -> Создать -> Проект…

Выберете из предложенных шаблонов, шаблон «Приложение Windows Forms Visual C#». Задайте имя проекта и нажмите кнопку OK.

После создания проекта, в обозревателе решений сделайте клик правой клавишей мыши по разделу «Ссылки» и выберете в появившемся контекстном меню пункт «Добавить ссылку…».

Выберете вкладку «Обзор» и укажите вашу библиотеку.
Если вы все успешно выполнили, в разделе «Ссылки» у вас появится название вашей библиотеки.
Сделайте клик правой клавишей мыши по главной форме вашего проекта и в открывшемся контекстном меню выберете пункт «Свойства».

В боковой панели откроются «Свойства» формы. Найдите метод Load и сделайте двойной клик левой клавишей мыши по нему, у вас откроется новая вкладка с добавленным методом Form1_Load.

Добавим пространство имен с названием нашей библиотеки.
using ClassLibrary1;
Пропишите вызов нескольких методов из библиотеки и вывод результата в диалоговом окне «MessageBox». Обратите внимание что при выборе методом из библиотеки у вас показывается подсказка которую мы прописывали. Если такого не происходит то, xml файл документации отсутствует в директории с библиотекой. Пример подсказок вы можете посмотреть на скриншоте ниже.

Запустите проект, нажав клавишу F5. И вы увидите результат выполнения методов прописанных в библиотеке, с параметрами которые вы передали при вызове.

Ссылка для скачивания примера библиотеки: Яндекс.Диск

Ссылка для скачивания примера: Яндекс.Диск

Данная статья с сайта - http://www.csharpcoderr.com/2013/01/dll.html


Создание библиотеки dll и подключение к проекту C# (Creating dll library and adding references to C# project)

Создание библиотеки dll и подключение к проекту C# (Creating dll library and adding references to C# project)

0. Введение

Начнем с того, что может понадобиться использовать методы одного класса несколько раз. Для этих целей служат библиотеки. Порой не совсем очевидно как их подключать в проект и использовать. Сегодня на простом примере разберем как создать библиотеку и подключить в наш проект.
1. Создание библиотеки

Для начала создадим проект:

Файл - Создать проект - Библиотека классов ( File - New project - Class Library)
Назовем ее ClassLibrary1.

Далее создадим метод, который потом нам пригодится (в нашем примере расчет процентной ставки вклада, в зависимости от введенных данных):

2. Создание проекта и подключение библиотеки ClassLibrary1

Создаем, например, консольное приложение и далее правой кнопкой нажимаем на название проекта и выбираем добавить ссылку (Add Reference):

Появится окно выбора библиотеки. Переходим на вкладку обзор (Browse) и выбираем библиотеку, созданную ранее (в нашем случае ClassLibrary1.dll):

Нажимаем OK и справа в обозревателе решений (Solution Explorer) во вкладке References должна быть написана библиотека выбранная выше:

Значит библиотека добавлена и можно ее использовать. Осталось еще подключить ее в самом коде программы: "using ClassLibrary1;".
В нашем случае вызов функции библиотеки происходит так:
Class1.Summ_Mass(summ, months, procent);
Название_класса.Метод (ввиду того, что в классе объявлен статический метод и нет необходимости создавать экземпляр класса)
Вот, собственно, и все, что необходимо, чтобы подключить и использовать библиотеку в своем проекте.

Данная статья с сайта - grenyavolki.blogspot.com


Работа с библиотеками динамической компоновки (DLL)

Работа с библиотеками динамической компоновки (DLL)

С самого рождения (или чуть позже) операционная система Windows использовала библиотеки динамической компоновки DLL (Dynamic Link Library), в которых содержались реализации наиболее часто применяемых функций. Наследники Windows - NT и Windows 95, а также OS/2 - тоже зависят от библиотек DLL в плане обеспечения значительной части их функциональных возможностей.

Рассмотрим ряд аспектов создания и использования библиотек DLL:

  • как статически подключать библиотеки DLL;
  • как динамически загружать библиотеки DLL;
  • как создавать библиотеки DLL;
  • как создавать расширения МFC библиотек DLL.

Использование DLL

Практически невозможно создать приложение Windows, в котором не использовались бы библиотеки DLL. В DLL содержатся все функции Win32 API и несчетное количество других функций операционных систем Win32.

Вообще говоря, DLL - это просто наборы функций, собранные в библиотеки. Однако, в отличие от своих статических родственников (файлов . lib), библиотеки DLL не присоединены непосредственно к выполняемым файлам с помощью редактора связей. В выполняемый файл занесена только информация об их местонахождении. В момент выполнения программы загружается вся библиотека целиком. Благодаря этому разные процессы могут пользоваться совместно одними и теми же библиотеками, находящимися в памяти. Такой подход позволяет сократить объем памяти, необходимый для нескольких приложений, использующих много общих библиотек, а также контролировать размеры ЕХЕ-файлов.

Однако, если библиотека используется только одним приложением, лучше сделать ее обычной, статической. Конечно, если входящие в ее состав функции будут использоваться только в одной программе, можно просто вставить в нее соответствующий файл с исходным текстом.

Чаще всего проект подключается к DLL статически, или неявно, на этапе компоновки. Загрузкой DLL при выполнении программы управляет операционная система. Однако, DLL можно загрузить и явно, или динамически, в ходе работы приложения.

Библиотеки импортирования

При статическом подключении DLL имя .lib-файла определяется среди прочих параметров редактора связей в командной строке или на вкладке "Link" диалогового окна "Project Settings" среды Developer Studio. Однако .lib-файл, используемый при неявном подключении DLL, - это не обычная статическая библиотека. Такие .lib-файлы называются библиотеками импортирования (import libraries). В них содержится не сам код библиотеки, а только ссылки на все функции, экспортируемые из файла DLL, в котором все и хранится. В результате библиотеки импортирования, как правило, имеют меньший размер, чем DLL-файлы. К способам их создания вернемся позднее. А сейчас рассмотрим другие вопросы, касающиеся неявного подключения динамических библиотек.

Согласование интерфейсов

При использовании собственных библиотек или библиотек независимых разработчиков придется обратить внимание на согласование вызова функции с ее прототипом.

Если бы мир был совершенен, то программистам не пришлось бы беспокоиться о согласовании интерфейсов функций при подключении библиотек - все они были бы одинаковыми. Однако мир далек от совершенства, и многие большие программы написаны с помощью различных библиотек без C++.

По умолчанию в Visual C++ интерфейсы функций согласуются по правилам C++. Это значит, что параметры заносятся в стек справа налево, вызывающая программа отвечает за их удаление из стека при выходе из функции и расширении ее имени. Расширение имен (name mangling) позволяет редактору связей различать перегруженные функции, т.е. функции с одинаковыми именами, но разными списками аргументов. Однако в старой библиотеке С функции с расширенными именами отсутствуют.

Хотя все остальные правила вызова функции в С идентичны правилам вызова функции в C++, в библиотеках С имена функций не расширяются. К ним только добавляется впереди символ подчеркивания (_).

Если необходимо подключить библиотеку на С к приложению на C++, все функции из этой библиотеки придется объявить как внешние в формате С:

    extern     "С" int MyOldCFunction(int myParam);

Объявления функций библиотеки обычно помещаются в файле заголовка этой библиотеки, хотя заголовки большинства библиотек С не рассчитаны на применение в проектах на C++. В этом случае необходимо создать копию файла заголовка и включить в нее модификатор extern "C" к объявлению всех используемых функций библиотеки. Модификатор extern "C" можно применить и к целому блоку, к которому с помощью директивы #tinclude подключен файл старого заголовка С. Таким образом, вместо модификации каждой функции в отдельности можно обойтись всего тремя строками:

    extern     "С"
    {
        #include "MyCLib.h"
    }

В программах для старых версий Windows использовались также соглашения о вызове функций языка PASCAL для функций Windows API. В новых программах следует использовать модификатор winapi, преобразуемый в _stdcall. Хотя это и не стандартный интерфейс функций С или C++, но именно он используется для обращений к функциям Windows API. Однако обычно все это уже учтено в стандартных заголовках Windows.

Загрузка неявно подключаемой DLL

При запуске приложение пытается найти все файлы DLL, неявно подключенные к приложению, и поместить их в область оперативной памяти, занимаемую данным процессом. Поиск файлов DLL операционной системой осуществляется в следующей последовательности.

  • Каталог, в котором находится ЕХЕ-файл.
  • Текущий каталог процесса.
  • Системный каталог Windows.

Если библиотека DLL не обнаружена, приложение выводит диалоговое окно с сообщением о ее отсутствии и путях, по которым осуществлялся поиск. Затем процесс отключается.

Если нужная библиотека найдена, она помещается в оперативную память процесса, где и остается до его окончания. Теперь приложение может обращаться к функциям, содержащимся в DLL.

Динамическая загрузка и выгрузка DLL

Вместо того, чтобы Windows выполняла динамическое связывание с DLL при первой загрузке приложения в оперативную память, можно связать программу с модулем библиотеки во время выполнения программы (при таком способе в процессе создания приложения не нужно использовать библиотеку импорта). В частности, можно определить, какая из библиотек DLL доступна пользователю, или разрешить пользователю выбрать, какая из библиотек будет загружаться. Таким образом можно использовать разные DLL, в которых реализованы одни и те же функции, выполняющие различные действия. Например, приложение, предназначенное для независимой передачи данных, сможет в ходе выполнения принять решение, загружать ли DLL для протокола TCP/IP или для другого протокола.

Загрузка обычной DLL

Первое, что необходимо сделать при динамической загрузке DLL, - это поместить модуль библиотеки в память процесса. Данная операция выполняется с помощью функции ::LoadLibrary, имеющей единственный аргумент - имя загружаемого модуля. Соответствующий фрагмент программы должен выглядеть так:

    HINSTANCE hMyDll;
    ::
    if((hMyDll=::    LoadLibrary("MyDLL"))==NULL) { /* не удалось загрузить DLL */ }
    else { /* приложение имеет право пользоваться функциями DLL через hMyDll */ }

Стандартным расширением файла библиотеки Windows считает .dll, если не указать другое расширение. Если в имени файла указан и путь, то только он будет использоваться для поиска файла. В противном случае Windows будет искать файл по той же схеме, что и в случае неявно подключенных DLL, начиная с каталога, из которого загружается exe-файл, и продолжая в соответствии со значением PATH.

Когда Windows обнаружит файл, его полный путь будет сравнен с путем библиотек DLL, уже загруженных данным процессом. Если обнаружится тождество, вместо загрузки копии приложения возвращается дескриптор уже подключенной библиотеки.

Если файл обнаружен и библиотека успешно загрузилась, функция ::LoadLibrary возвращает ее дескриптор, который используется для доступа к функциям библиотеки.

Перед тем, как использовать функции библиотеки, необходимо получить их адрес. Для этого сначала следует воспользоваться директивой typedef для определения типа указателя на функцию и определить переменную этого нового типа, например:

        // тип PFN_MyFunction будет объявлять указатель на функцию,
    // принимающую указатель на символьный буфер и выдающую значение типа int
    typedef int (WINAPI *PFN_MyFunction)(char *);
    ::
    PFN_MyFunction pfnMyFunction;

Затем следует получить дескриптор библиотеки, при помощи которого и определить адреса функций, например адрес функции с именем MyFunction:

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

Адрес функции определяется при помощи функции ::GetProcAddress, ей следует передать имя библиотеки и имя функции. Последнее должно передаваться в том виде, в котором экспортируется из DLL.

Можно также сослаться на функцию по порядковому номеру, по которому она экспортируется (при этом для создания библиотеки должен использоваться def-файл, об этом будет рассказано далее):

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

После завершения работы с библиотекой динамической компоновки, ее можно выгрузить из памяти процесса с помощью функции ::FreeLibrary:

    ::FreeLibrary(hMyDll);

Загрузка MFC-расширений динамических библиотек

При загрузке MFC-расширений для DLL (подробно о которых рассказывается далее) вместо функций LoadLibraryи FreeLibrary используются функции AfxLoadLibrary и AfxFreeLibrary. Последние почти идентичны функциям Win32 API. Они лишь гарантируют дополнительно, что структуры MFC, инициализированные расширением DLL, не были запорчены другими потоками.

Ресурсы DLL

Динамическая загрузка применима и к ресурсам DLL, используемым MFC для загрузки стандартных ресурсов приложения. Для этого сначала необходимо вызвать функцию LoadLibrary и разместить DLL в памяти. Затем с помощью функции AfxSetResourceHandle нужно подготовить окно программы к приему ресурсов из вновь загруженной библиотеки. В противном случае ресурсы будут загружаться из файлов, подключенных к выполняемому файлу процесса. Такой подход удобен, если нужно использовать различные наборы ресурсов, например для разных языков.

Замечание. С помощью функции LoadLibrary можно также загружать в память исполняемые файлы (не запускать их на выполнение!). Дескриптор выполняемого модуля может затем использоваться при обращении к функциям FindResource и LoadResource для поиска и загрузки ресурсов приложения. Выгружают модули из памяти также при помощи функции FreeLibrary.

Пример обычной DLL и способов загрузки

Приведем исходный код динамически подключаемой библиотеки, которая называется MyDLL и содержит одну функцию MyFunction, которая просто выводит сообщение.

Сначала в заголовочном файле определяется макроконтстанта EXPORT. Использование этого ключевого слова при определении некоторой функции динамически подключаемой библиотеке позволяет сообщить компоновщику, что эта функция доступна для использования другими программами, в результате чего он заносит ее в библиотеку импорта. Кроме этого, такая функция, точно так же, как и оконная процедура, должна определяться с помощью константы CALLBACK:

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

Файл библиотеки также несколько отличается от обычных файлов на языке C для Windows. В нем вместо функции WinMain имеется функция DllMain. Эта функция используется для выполнения инициализации, о чем будет рассказано позже. Для того, чтобы библиотека осталась после ее загрузки в памяти, и можно было вызывать ее функции, необходимо, чтобы ее возвращаемым значением было TRUE:

MyDLL.c
    #include <windows.h>
    #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;
    }

После трансляции и компоновки этих файлов появляется два файла - MyDLL.dll (сама динамически подключаемая библиотека) и MyDLL.lib (ее библиотека импорта).

Пример неявного подключения DLL приложением

Приведем теперь исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll:

    #include <windows.h>
    #include "MyDLL.h"

    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
            LPSTR lpCmdLine, int nCmdShow)
    {
        int iCode=MyFunction("Hello");
        return 0;
    }

Эта программа выглядит как обычная программ для Windows, чем она в сущности и является. Тем не менее, следует обратить внимание, что в исходный ее текст помимо вызова функции MyFunction из DLL-библиотеки включен и заголовочный файл этой библиотеки MyDLL.h. Также необходимо на этапе компоновки приложения подключить к нему библиотеку импорта MyDLL.lib (процесс неявного подключения DLL к исполняемому модулю).

Чрезвычайно важно понимать, что сам код функции MyFunction не включается в файл MyApp.exe. Вместо этого там просто имеется ссылка на файл MyDLL.dll и ссылка на функцию MyFunction, которая находится в этом файле. Файл MyApp.exe требует запуска файла MyDLL.dll.

Заголовочный файл MyDLL.h включен в файл с исходным текстом программы MyApp.c точно так же, как туда включен файл windows.h. Включение библиотеки импорта MyDLL.lib для компоновки аналогично включению туда всех библиотек импорта Windows. Когда программа MyApp.exe работает, она подключается к библиотеке MyDLL.dll точно так же, как ко всем стандартным динамически подключаемым библиотекам Windows.

Пример динамической загрузки DLL приложением

Приведем теперь полностью исходный код простого приложения, которое использует функцию MyFunction из библиотеки MyDLL.dll, используя динамическую загрузку библиотеки:

    #include <windows.h> typedef int (WINAPI

    *PFN_MyFunction)(char *); int WINAPI WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance, LPSTR lpCmdLine,
    int
        nCmdShow) {
        HINSTANCEhMyDll;if((hMyDll=LoadLibrary("MyDLL"))==NULL) return 1;

        PFN_MyFunction pfnMyFunction;
        pfnMyFunction=(PFN_MyFunction)GetProcAddress(hMyDll,"MyFunction");
        int iCode=(*pfnMyFunction)("Hello");

        FreeLibrary(hMyDll);
        return 0;
    }

Создание DLL

Теперь, познакомившись с принципами работы библиотек DLL в приложениях, рассмотрим способы их создания. При разработке приложении функции, к которым обращается несколько процессов, желательно размещать в DLL. Это позволяет более рационально использовать память в Windows.

Проще всего создать новый проект DLL с помощью мастера AppWizard, который автоматически выполняет многие операции. Для простых DLL, таких как рассмотренные в этой главе, необходимо выбрать тип проекта Win32 Dynamic-Link Library. Новому проекту будут присвоены все необходимые параметры для создания библиотеки DLL. Файлы исходных текстов придется добавлять к проекту вручную.

Если же планируется в полной мере использовать функциональные возможности MFC, такие как документы и представления, или намерены создать сервер автоматизации OLE, лучше выбрать тип проекта MFC AppWizard (dll). В этом случае, помимо присвоения проекту параметров для подключения динамических библиотек, мастер проделает некоторую дополнительную работу. В проект будут добавлены необходимые ссылки на библиотеки MFC и файлы исходных текстов, содержащие описание и реализацию в библиотеке DLL объекта класса приложения, производного от CWinApp.

Иногда удобно сначала создать проект типа MFC AppWizard (dll) в качестве тестового приложения, а затем - библиотеку DLL в виде его составной части. В результате DLL в случае необходимости будет создаваться автоматически.

Функция DllMain

Большинство библиотек DLL - просто коллекции практически независимых друг от друга функций, экспортируемых в приложения и используемых в них. Кроме функций, предназначенных для экспортирования, в каждой библиотеке DLL есть функция DllMain. Эта функция предназначена для инициализации и очистки DLL. Она пришла на смену функциям LibMain и WEP, применявшимся в предыдущих версиях Windows. Структура простейшей функции DllMainможет выглядеть, например, так:

    BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved)
    {
        BOOL bAllWentWell=TRUE;
        switch (dwReason)
        {
            case DLL_PROCESS_ATTACH:     // Инициализация процесса.
                break;
            case DLL_THREAD_ATTACH:     // Инициализация потока.
                break;
            case DLL_THREAD_DETACH:     // Очистка структур потока.
                break;
            case DLL_PROCESS_DETACH:     // Очистка структур процесса.
                break;
        }
        if(bAllWentWell)     return TRUE;
        else            return FALSE;
    }

Функция DllMain вызывается в нескольких случаях. Причина ее вызова определяется параметром dwReason, который может принимать одно из следующих значений.

При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового потока DllMainO вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).

По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.

Все операции по инициализации и очистке для процессов и потоков, в которых нуждается DLL, необходимо выполнять на основании значения dwReason, как было показано в предыдущем примере. Инициализация процессов обычно ограничивается выделением ресурсов, совместно используемых потоками, в частности загрузкой разделяемых файлов и инициализацией библиотек. Инициализация потоков применяется для настройки режимов, свойственных только данному потоку, например для инициализации локальной памяти.

В состав DLL могут входить ресурсы, не принадлежащие вызывающему эту библиотеку приложению. Если функции DLL работают с ресурсами DLL, было бы, очевидно, полезно сохранить где-нибудь в укромном месте дескриптор hInst и использовать его при загрузке ресурсов из DLL. Указатель IpReserved зарезервирован для внутреннего использования Windows. Следовательно, приложение не должно претендовать на него. Можно лишь проверить его значение. Если библиотека DLL была загружена динамически, оно будет равно NULL. При статической загрузке этот указатель будет ненулевым.

В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.

Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандартную версию, которая просто возвращает TRUE.

Экспортирование функций из DLL

Чтобы приложение могло обращаться к функциям динамической библиотеки, каждая из них должна занимать строку в таблице экспортируемых функций DLL. Есть два способа занести функцию в эту таблицу на этапе компиляции.

Метод __declspec (dllexport)

Можно экспортировать функцию из DLL, поставив в начале ее описания модификатор __declspec (dllexport) . Кроме того, в состав MFC входит несколько макросов, определяющих __declspec (dllexport), в том числе AFX_CLASS_EXPORT, AFX_DATA_EXPORT и AFX_API_EXPORT.

Метод __declspec применяется не так часто, как второй метод, работающий с файлами определения модуля (.def), и позволяет лучше управлять процессом экспортирования.

Файлы определения модуля

Синтаксис файлов с расширением .def в Visual C++ достаточно прямолинеен, главным образом потому, что сложные параметры, использовавшиеся в ранних версиях Windows, в Win32 более не применяются. Как станет ясно из следующего простого примера, .def-файл содержит имя и описание библиотеки, а также список экспортируемых функций:

MyDLL.def
    LIBRARY         "MyDLL"
        DESCRIPTION    'MyDLL - пример DLL-библиотеки'

    EXPORTS
        MyFunction     @1

В строке экспорта функции можно указать ее порядковый номер, поставив перед ним символ @. Этот номер будет затем использоваться при обращении к GetProcAddress (). На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако способ, которым он это делает, отчасти непредсказуем, если не присвоить эти номера явно.

В строке экспорта можно использовать параметр NONAME. Он запрещает компилятору включать имя функции в таблицу экспортирования DLL:

        MyFunction     @1 NONAME

Иногда это позволяет сэкономить много места в файле DLL. Приложения, использующие библиотеку импортирования для неявного подключения DLL, не "заметят" разницы, поскольку при неявном подключении порядковые номера используются автоматически. Приложениям, загружающим библиотеки DLL динамически, потребуется передавать в GetProcAddress порядковый номер, а не имя функции.

При использовании вышеприведенного def-файл описания экспортируемых функций DLL-библиотеки может быть,например, не таким:

    #define EXPORT extern "C" __declspec (dllexport)
    EXPORT int CALLBACK MyFunction(char *str);
    a таким:
    extern "C" int CALLBACK MyFunction(char *str);

Экспортирование классов

Создание .def-файла для экспортирования даже простых классов из динамической библиотеки может оказаться довольно сложным делом. Понадобится явно экспортировать каждую функцию, которая может быть использована внешним приложением.

Если взглянуть на реализованный в классе файл распределения памяти, в нем можно заметить некоторые весьма необычные функции. Оказывается, здесь есть неявные конструкторы и деструкторы, функции, объявленные в макросах MFC, в частности _DECLARE_MESSAGE_MAP, а также функции, которые написанные программистом.

Хотя можно экспортировать каждую из этих функций в отдельности, есть более простой способ. Если в объявлении класса воспользоваться макромодификатором AFX_CLASS_EXPORT, компилятор сам позаботится об экспортировании необходимых функций, позволяющих приложению использовать класс, содержащийся в DLL.

Память DLL

В отличие от статических библиотек, которые, по существу, становятся частью кода приложения, библиотеки динамической компоновки в 16-разрядных версиях Windows работали с памятью несколько иначе. Под управлением Win 16 память DLL размещалась вне адресного пространства задачи. Размещение динамических библиотек в глобальной памяти обеспечивало возможность совместного использования их различными задачами.

В Win32 библиотека DLL располагается в области памяти загружающего ее процесса. Каждому процессу предоставляется отдельная копия "глобальной" памяти DLL, которая реинициализируется каждый раз, когда ее загружает новый процесс. Это означает, что динамическая библиотека не может использоваться совместно, в общей памяти, как это было в Winl6.

И все же, выполнив ряд замысловатых манипуляций над сегментом данных DLL, можно создать общую область памяти для всех процессов, использующих данную библиотеку.

Допустим, имеется массив целых чисел, который должен использоваться всеми процессами, загружающими данную DLL. Это можно запрограммировать следующим образом:

    #pragma data_seg(".myseg")
    int sharedlnts[10] ;
        // другие переменные общего пользования
    #pragma data_seg()
    #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

Все переменные, объявленные между директивами #pragma data_seg(), размещаются в сегменте .myseg. Директива #pragma comment () - не обычный комментарий. Она дает указание библиотеке выполняющей системы С пометить новый раздел как разрешенный для чтения, записи и совместного доступа.

Полная компиляция DLL

Если проект динамической библиотеки создан с помощью AppWizard и .def-файл модифицирован соответствующим образом - этого достаточно. Если же файлы проекта создаются вручную или другими способами без помощи AppWizard, в командную строку редактора связей следует включить параметр /DLL. В результате вместо автономного выполняемого файла будет создана библиотека DLL.

Если в .def-файле есть строка LIBRART, указывать явно параметр /DLL в командной строке редактора связей не нужно.

Для MFC предусмотрен ряд особых режимов, касающихся использования динамической библиотекой библиотек MFC. Этому вопросу посвящен следующий раздел.

DLL и MFC

Программист не обязан использовать MFC при создании динамических библиотек. Однако использование MFC открывает ряд очень важных возможностей.

Имеется два уровня использования структуры MFC в DLL. Первый из них - это обычная динамическая библиотека на основе MFC, MFC DLL (regular MFC DLL). Она может использовать MFC, но не может передавать указатели на объекты MFC между DLL и приложениями. Второй уровень реализован в динамических расширениях MFC (MFC extensions DLL). Использование этого вида динамических библиотек требует некоторых дополнительных усилий по настройке, но позволяет свободно обмениваться указателями на объекты MFC между DLL и приложением.

Обычные MFC DLL

Обычные MFC DLL позволяют применять MFC в динамических библиотеках. При этом приложения, обращающиеся к таким библиотекам, не обязательно должны быть построены на основе MFC. В обычных DLL можно использовать MFC любым способом, в том числе создавая в DLL новые классы на базе классов MFC и экспортируя их в приложения.

Однако обычные DLL не могут обмениваться с приложениями указателями на классы, производные от MFC.

Если приложению необходимо обмениваться с DLL указателями на объекты классов MFC или их производных, нужно использовать расширение DLL, описанное в следующем разделе.

Архитектура обычных DLL рассчитана на использование другими средами программирования, такими как Visual Basic и PowerBuilder.

При создании обычной библиотеки MFC DLL с помощью AppWizard выбирается новый проект типа MFC AppWizard (dll). В первом диалоговом окне мастера приложений необходимо выбрать один из режимов для обычных динамических библиотек: "Regular DLL with MFC statistically linked" или "Regular DLL using shared MFC DLL". Первый предусматривает статическое, а второй - динамическое подключение библиотек MFC. Впоследствии режим подключения MFC к DLL можно будет изменить с помощью комбинированного списка на вкладке "General" диалогового окна "Project settings".

Управление информацией о состоянии MFC

В каждом модуле процесса MFC содержится информация о его состоянии. Таким образом, информация о состоянии DLL отлична от информации о состоянии вызвавшего ее приложения. Поэтому любые экспортируемые из библиотеки функции, обращение к которым исходит непосредственно из приложений, должны сообщать MFC, какую информацию состояния использовать. В обычной MFC DLL, использующей динамические библиотеки MFC, перед вызовом любой подпрограммы MFC в начале экспортируемой функции нужно поместить следующую строку:

    AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

Данный оператор определяет использование соответствующей информации о состоянии во время выполнения функции, обратившейся к данной подпрограмме.

Динамические расширения MFC

MFC позволяет создавать такие библиотеки DLL, которые воспринимаются приложениями не как набор отдельных функций, а как расширения MFC. С помощью данного вида DLL можно создавать новые классы, производные от классов MFC, и использовать их в своих приложениях.

Чтобы обеспечить возможность свободного обмена указателями на объекты MFC между приложением и DLL, нужно создать динамическое расширение MFC. DLL этого типа подключаются к динамическим библиотекам MFC так же, как и любые приложения, использующие динамическое расширение MFC.

Чтобы создать новое динамическое расширение MFC, проще всего, воспользовавшись мастером приложении, присвоить проекту тип MFC AppWizard (dll) и на шаге 1 включить режим "MFC Extension DLL". В результате новому проекту будут присвоены все необходимые атрибуты динамического расширения MFC. Кроме того, будет создана функция DllMain для DLL, выполняющая ряд специфических операций по инициализации расширения DLL. Следует обратить внимание, что динамические библиотеки данного типа не содержат и не должны содержать объектов, производных от CWinApp.

Инициализация динамических расширений

Чтобы "вписаться" в структуру MFC, динамические расширения MFC требуют дополнительной начальной настройки. Соответствующие операции выполняются функцией DllMain. Рассмотрим пример этой функции, созданный мастером AppWizard.

    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
    }

Самой важной частью этой функции является вызов AfxInitExtensionModule. Это инициализация динамической библиотеки, позволяющая ей корректно работать в составе структуры MFC. Аргументами данной функции являются передаваемый в DllMain дескриптор библиотеки DLL и структура AFX_EXTENSION_MODULE, содержащая информацию о подключаемой к MFC динамической библиотеке.

Нет необходимости инициализировать структуру AFX_EXTENSION_MODULE явно. Однако объявить ее нужно обязательно. Инициализацией же займется конструктор CDynLinkLibrary. В DLL необходимо создать класс CDynLinkLibrary. Его конструктор не только будет инициализировать структуру AFX_EXTENSION_MODULE, но и добавит новую библиотеку в список DLL, с которыми может работать MFC.

Загрузка динамических расширений MFC

Начиная с версии 4.0 MFC позволяет динамически загружать и выгружать DLL, в том числе и расширения. Для корректного выполнения этих операций над создаваемой DLL в ее функцию DllMain в момент отключения от процесса необходимо добавить вызов AfxTermExtensionModule. Последней функции в качестве параметра передается уже использовавшаяся выше структура AFX_EXTENSION_MODULE. Для этого в текст DllMain нужно добавить следующие строки.

    if(dwReason == DLL_PROCESS_DETACH)
    {
        AfxTermExtensionModule(MyExtDLL);
    }

Кроме того, следует помнить, что новая библиотека DLL является динамическим расширением и должна загружаться и выгружаться динамически, с помощью функций AfxLoadLibrary и AfxFreeLibrary,а не LoadLibrary и FreeLibrary.

Экспортирование функций из динамических расширений

Рассмотрим теперь, как осуществляется экспортирование в приложение функций и классов из динамического расширения. Хотя добавить в DEF-файл все расширенные имена можно и вручную, лучше использовать модификаторы для объявлений экспортируемых классов и функций, такие как AFX_EXT_CLASS и AFX_EXT_API,например:

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

данная статься с сайта - www.codenet.ru


Создание и использование DLL в Delphi

Создание и использование DLL в Delphi

 PDF Печать
Статьи

Динамическая библиотека, или DLL, является совокупностью подпрограмм (маленьких программ), которые могут вызываться приложениями или другими DLL. Подобно модулям, DLL содержит общий код или ресурсы, которые могут использовать несколько приложений одновременно из одного экземпляра DLL. В сущности Windows - это просто совокупность DLL.

Естественно, используя Delphi, мы можем тоже написать и использовать наши собственные DLL и можем вызывать функции в DLL, разработанном другими разработчиками и на других системах (типа Visual Basicили C/C++).

Создание динамической библиотеки

Следующие несколько строк демонстрируют, как создать простой DLL, используя Delphi.

Начинающим Дельфистам нужно из меню выбрать File | New... DLL. Это создаст шаблон нового DLL в окне редактора. Выделите заданный по умолчанию текст и замените его следующим кодом:

library TestLibrary;

uses SysUtils, Classes, Dialogs;

procedure DllMessage; export;
begin
  ShowMessage('Hello world from a Delphi DLL') ;
end;

exports DllMessage;

begin
end.

Если Вы посмотрите на файл проекта любого приложения Delphi, Вы увидите, что он начинается с зарезервированного слова Program. DLL в отличие от этого, начинается с зарезервированного слова Library. Затем это сопровождается пунктом uses для любых необходимых модулей. В этом простом примере за этим следует процедура по имени DllMessage, которая не делает ничего, кроме показа простого сообщения.

В конце этого текста, стоит инструкция exports. Здесь включен список подпрограмм, которые будут экспортированы из DLL и могут вызываться другими приложениями. Это означает, что, например, в DLLнаходится 5 процедур и только 2 из них (перечисленных в разделе exports) могут вызываться из внешних программ (3 из них подпрограммы в DLL).

Чтобы использовать этот простой DLL, мы должны скомпилировать его, нажав Ctrl+F9. Это создасть DLL по имени SimpleMessageDll.dll в папке проекта.

А теперь давайте посмотрим, как вызвать процедуру DllMessage из (статически загруженной) DLL.

Чтобы импортировать процедуру, содержащуюся в DLL, мы используем ключевое слово external в объявлении процедуры. Например, объявление процедуры DllMessage, показанной ранее, выглядела бы так:

procedure DllMessage; external 'SimpleMessageDLL.dll'

А вызов процедуры выглядит так:

DllMessage;

Полный код для формы Form1 с TButton по имени Button1 (для вызова функции DllMessage) выглядит так:

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;

var
   Form1: TForm1;

  procedure DllMessage; external 'SimpleMessageDLL.dll'

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject) ;
begin
   DllMessage;
end;

end.

Это все! Все просто, как и все в Delphi!

Данная статья с сайта - http://delphiru.ru/resources/26-creating-and-using-dll-from-delphi


Использование и создание DLL в Delphi

Использование и создание DLL в Delphi

Введение

В связи с бурным развитием технологий программирования, все больше людей сталкиваются с проблемой наращивания возможностей своих программ. Данная статья посвящена именно этому вопросу, а именно - программирование DLL в Borland Delphi. Кроме того, так как мы затронем вопросы по использованию библиотек DLL, то попутно коснемся импортирования функций из чужих DLL (в том числе и системных, т.е. WinAPI).

Области применения DLL

Итак, зачем же нужны библиотеки DLL и где они используются?.. Перечислим лишь некоторые из областей их применения:

  • Отдельные библиотеки, содержащие полезные для программистов дополнительные функции. Например, функции для работы со строками, или же - сложные библиотеки для преобразования изображений.
  • Хранилища ресурсов. В DLL можно хранить не только программы и функции, но и всевозможные ресурсы - иконки, рисунки, строковые массивы, меню, и т.д.
  • Библиотеки поддержки. В качестве примера можно привести библиотеки таких известных пакетов, как: DirectX, ICQAPI (API для ICQ), OpenGL и т.д.
  • Части программы. Например, в DLL можно хранить окна программы (формы), и т.п.
  • Плагины (Plugins). - Вот где настоящий простор для мыслей программиста! Плагины - дополнения к программе, расширяющие ее возможности. Например, в этой статье мы рассмотрим теорию создания плагина для собственной программы.
  • Разделяемый ресурс. DLL (Dynamic Link Library) может быть использована сразу несколькими программами или процессами (т.н. sharing - разделяемый ресурс)

Краткое описание функций и приемов для работы с DLL

Итак, какие же приемы и функции необходимо использовать, чтобы работать с DLL? Разберем два метода импортирования функций из библиотеки:

1 способ. Привязка DLL к программе. Это наиболее простой и легкий метод для использования функций, импортируемых из DLL. Однако (и на это следует обратить внимание) этот способ имеет очень весомый недостаток - если библиотека, которую использует программа, не будет найдена, то программа просто не запустится, выдавая ошибку и сообщая о том, что ресурс DLL не найден. А поиск библиотеки будет вестись: в текущем каталоге, в каталоге программы, в каталоге WINDOWS\SYSTEM, и т.д.
Итак, для начала - общая форма этого приема:

implementation
...
function FunctionName(Par1: Par1Type; Par2: Par2Type; ...): ReturnType; stdcall; external 'DLLNAME.DLL' name'FunctionName' index FuncIndex;
// или (если не функция, а процедура):
procedure ProcedureName(Par1: Par1Type; Par2: Par2Type; ...); stdcall; external 'DLLNAME.DLL' name 'ProcedureName' index ProcIndex;

Здесь: FunctionName (либо ProcedureName) - имя функции (или процедуры), которое будет использоваться в Вашей программе;
Par1, Par2, ... - имена параметров функции или процедуры;
Par1Type, Par2Type, ... - типы параметров функции или процедуры (например, Integer);
ReturnType - тип возвращаемого значения (только для функции);
stdcall - директива, которая должна точно совпадать с используемой в самой DLL;
external 'DLLNAME.DLL' - директива, указывающая имя внешней DLL, из которой будет импортирована данная функция или процедура (в данном случае - DLLNAME.DLL);
name 'FunctionName' ('ProcedureName') - директива, указывающая точное имя функции в самой DLL. Это необязательная директива, которая позволяет использовать в программе функцию, имеющую название, отличное от истинного (которое она имеет в библиотеке);
index FunctionIndex (ProcedureIndex) - директива, указывающая порядковый номер функции или процедуры в DLL. Это также необязательная директива.

2 способ. Динамическая загрузка DLL. Это гораздо более сложный, но и более элегантный метод. Он лишен недостатка первого метода. Единственное, что неприятно - объем кода, необходимого для осуществления этого приема, причем сложность в том, что функция, импортируемая из DLL достуна лишь тогда, когда эта DLL загружена и находится в памяти... С примером можно ознакомиться ниже, а пока - краткое описание используемых этим методом функций WinAPI:

LoadLibrary(LibFileName: PChar) - загрузка указанной библиотеки LibFileName в память. При успешном завершении функция возвращает дескриптор (THandle) DLL в памяти.
GetProcAddress(Module: THandle; ProcName: PChar) - считывает адpес экспоpтиpованной библиотечной функции. При успешном завершении функция возвращает дескриптор (TFarProc) функции в загруженной DLL.
FreeLibrary(LibModule: THandle) - делает недействительным LibModule и освобождает связанную с ним память. Следует заметить, что после вызова этой процедуры функции данной библиотеки больше недоступны.

Практика и примеры

Ну а теперь пора привести пару примеров использования вышеперечисленных методов и приемов:

Пример 1. Привязка DLL к программе

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}

implementation

{Определяем внешнюю библиотечную функцию}

function GetSimpleText(LangRus: Boolean): PChar; stdcall; external 'MYDLL.DLL';

procedure Button1Click(Sender: TObject);
begin
{И используем ее}
ShowMessage(StrPas(GetSimpleText(True)));
ShowMessage(StrPas(GetSimpleText(False)));
{ShowMessage - показывает диалоговое окно с указанной надписью; StrPas - преобразует строку PChar в string}
end;

Теперь то же самое, но вторым способом - с динамической загрузкой:

Пример 2. Динамическая загрузка DLL

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}

var
Form1: TForm1;
GetSimpleText: function(LangRus: Boolean): PChar;
LibHandle: THandle;

procedure Button1Click(Sender: TObject);
begin
{"Чистим" адрес функции от "грязи"}
@GetSimpleText := nil;
{Пытаемся загрузить библиотеку}
LibHandle := LoadLibrary('MYDLL.DLL');
{Если все OK}
if LibHandle >= 32 then begin
{...то пытаемся получить адрес функции в библиотеке}
@GetSimpleText := GetProcAddress(LibHandle,'GetSimpleText');
{Если и здесь все OK}
if @GetSimpleText <> nil then
{...то вызываем эту функцию и показываем результат}
ShowMessage(StrPas(GetSimpleText(True)));
end;
{И не забываем освободить память и выгрузить DLL}
FreeLibrary(LibHandle);
end;

ПРИМЕЧАНИЕ: Следует воздерживаться от использования типа string в библиотечных функциях, т.к. при его использовании существуют проблемы с "разделением памяти". Подробней об этом можно прочитать (правда, на английском) в тексте пустого проекта DLL, который создает Delphi (File -> New -> DLL). Так что лучше используйте PChar, а затем при необходимости конвертируйте его в string функцией StrPas.

Ну а теперь разберем непосредственно саму библиотеку DLL:

Пример 3. Исходник проекта MYDLL.DPR

library mydll;

uses SysUtils, Classes;

{Определяем функцию как stdcall}
function GetSimpleText(LangRus: Boolean): PChar; stdcall;
begin
{В зависимости от LangRus возвращаем русскую (True) либо английскую (False) фразу}
if LangRus then
Result := PChar('Здравствуй, мир!')
else
Result := PChar('Hello, world!');
end;

{Директива exports указывает, какие функции будут экспортированы этой DLL}
exports GetSimpleText;

begin
end.

Размещение в DLL ресурсов и форм

В DLL можно размещать не только функции, но и курсоры, рисунки, иконки, меню, текстовые строки. На этом мы останавливаться не будем. Замечу лишь, что для загрузки ресурса нужно загрузить DLL, а затем, получив ее дескриптор, - загружать сам ресурс соотвествующей функцией (LoadIcon, LoadCursor, и т.д.). В этом разделе мы лишь немного затронем размещение в библиотеках DLL окон приложения (т.е. форм в Дельфи).

Для этого нужно создать новую DLL и добавить в нее новую форму (File -> New -> DLL, а затем - File -> New Form). Далее, если форма представляет собой диалоговое окно (модальную форму (bsDialog)), то добавляем в DLL следующую функцию (допустим, форма называется Form1, а ее класс - TForm1):

Пример 4. Размещение формы в DLL

function ShowMyDialog(Msg: PChar): Boolean; stdcall;

...
exports ShowMyDialog;

function ShowMyDialog(Msg: PChar): Boolean;
begin
{Создаем экземпляр Form1 формы TForm1}
Form1 := TForm1.Create(Application);
{В Label1 выводим Msg}
Form1.Label1.Caption := StrPas(Msg);
{Возвращаем True только если нажата OK (ModalResult = mrOk)}
Result := (Form1.ShowModal = mrOk);
{Освобождаем память}
Form1.Free;
end;

Если же нужно разместить в DLL немодальную форму, то необходимо сделать две функции - открытия и закрытия формы. При этом нужно заставить DLL запомнить дескриптор этой формы.

Создание плагинов

Здесь мы не будем подробно рассматривать плагины, т.к. уже приведенные выше примеры помогут Вам легко разобраться в львиной части программирования DLL. Напомню лишь, что плагин - дополнение к программе, расширяющее ее возможности. При этом сама программа обязательно должна предусматривать наличие таких дополнений и позволять им выполнять свое предназначение.

Т.е., например, чтобы создать плагин к графическому редактору, который бы выполнял преобразование изображений, Вам нужно предусмотреть как минимум две функции в плагине (и, соответственно, вызвать эти функции в программе) - функция, которая бы возвращала имя плагина (и/или его тип), чтобы добавить этот плагин в меню (или в тулбар), плюс главная функция - передачи и приема изображения. Т.е. сначала программа ищет плагины, потом для каждого найденного вызывает его опозновательную функцию со строго определенным именем (например, GetPluginName) и добавляет нужный пункт в меню, затем, если пользователь выбрал этот пункт - вызывает вторую функцию, которой передает входное изображение (либо имя файла, содержащего это изображение), а эта функция, в свою очередь, обрабатывает изображение и возвращает его в новом виде (или имя файла с новым изображением). Вот и вся сущность плагина... :-)

Данная статья с сайта - www.helloworld.ru


Разработка и использование dll-библиотек

Help > Dll >
Разработка и использование dll-библиотек
Разработка и использование dll-библиотек

Dll-библиотека – это набор подпрограмм, которые могут использоваться приложением или другой dll-библиотекой. Так же как и модули языка Паскаль dll-библиотеки могут содержать разделяемый код или ресурсы. Но в отличие от модулей dll-библиотека является отдельно компилируемым исполняемым кодом, который подключается к вызывающему приложению не во время компиляции, а во время работы приложения. Это означает, что библиотека может отсутствовать на компьютере во время компиляции приложения, но в тоже время это означает, что во время компиляции нельзя проверить возможность импорта подпрограммы.

1. Соглашения о вызовах

При вызовах процедур и функций каждая программа использует определённые соглашения о вызовах. Эти соглашения определяют следующие параметры вызова:

  • порядок передачи параметров, в том числе использование регистров;
  • порядок очистки стека;
  • преобразование имени подпрограммы.

При разработке и использовании dll-библиотек, особенно если библиотека и использующая её программа написаны на разных языках программирования, необходимо следить за тем, чтобы при реализации и вызове подпрограмм использовались одни и те же соглашения о вызовах.

1.1. Соглашения о вызовах языка С++

Основными соглашениями о вызовах в языке С++ являются _cdecl, _stdcall, _fastcall и _thiscall. По умолчанию используется _cdecl. Модификатор, определяющий, соглашение о вызовах записывается после типа результата функции:
void _stdcall f() { … }

  • Все перечисленные соглашения о вызовах языка С++ подразумевают передачу параметров функции через стек справа налево, т.е. первым в стек кладётся самый правый (последний) параметр.
  • При использовании соглашения о вызовах _fastcall первые два параметра, размер которых не больше двойного слова, передаются через регистры ECX и EDX. Остальные параметры передаются через стек.
  • Соглашение о вызовах _thiscall используется для функций-членов класса: указатель this передаётся через регистр ECX, а остальные параметры – через стек.
  • Соглашение о вызовах _cdecl подразумевает, что очистка стека осуществляется вызывающей программой. Остальные соглашения о вызовах подразумевают, что очистка стека осуществляется подпрограммой.
  • Все перечисленные соглашения о вызовах подразумевают использование регистров для передачи результата функции. Если размер результата не превышает двойного слова, результат передаётся через регистр EAX. Если размер результата не превышает 8 байт, результат передаётся через регистровую пару EDX:EAX. В противном случае через регистр EAX передаётся адрес результата функции.

Соглашение о вызовах | Порядок передачи параметров | Очистка стека | Использование регистров
_cdecl | Справа налево | Вызывающей программой | EAX или EDX:EAX для передачи результата.
_stdcall | Справа налево | Подпрограммой | EAX или EDX:EAX для передачи результата.
_fastcall | Справа налево | Подпрограммой | ECX и EDX для передачи двух первых параметров. EAX или EDX:EAX для передачи результата.
_thiscall | Справа налево | Подпрограммой | ECX для передачи указателя this. EAX или EDX:EAX для передачи результата.

При использовании соглашения о вызовах _cdecl генерируемый код получается больше, чем при использовании соглашения о вызовах _stdcall, т.к. после каждого вызова функции необходимо вставлять команды очистки стека. Однако это соглашение о вызовах позволяет реализовывать и вызывать функции с переменным числом параметров.

1.2. Преобразование имён

Компоновщик использует преобразованные (decorated) имена функций. Для языка С++ в такое имя добавляется информации о классе или пространстве имён, которому принадлежит функция, о типах параметров и результата, о соглашении о вызовах.

Функция | Преобразованное имя
int a(char) {int i = 3; return i; } | ?a@@YAHD@Z
void __stdcall b::c(float) { } | ?c@b@@AAGXM@Z

Для языка С способ преобразования имени зависит от соглашения о вызовах.

Соглашение о вызовах | Преобразование имени
_cdecl | Перед именем функции добавляется знак подчёркивания
_stdcall | Перед именем функции добавляется знак подчёркивания, а после имени функции – знак @ и количество байт, занимаемых параметрами функции
_fastcall | То же, что и для _stdcall, но перед именем функции добавляется знак @

Функция | Преобразованное имя
extern "C" int f(char, char *){ } | _f
extern "C" int _stdcall f(char, char *){ } | _f@5
extern "C" int _fastcall f(char, char *){ } | @f@5

Знать и использовать преобразованные имена обычно не нужно. Но при использовании в программах на С++ dll-библиотек, написанных на других языках в прототипе функции может потребоваться директива extern "C", которая указывает, что необходимо применять правила компоновки языка С.

1.3. Соглашения о вызовах языка Паскаль

В языке Паскаль для указания соглашения о вызовах используются директивы register, pascal, cdecl, stdcall и safecall. Директива размещается в конце заголовка подпрограммы:
function MyFunction(x: real): real; cdecl;

По умолчанию подпрограммы в языке Паскаль компилируются с использованием соглашения о вызовах register.

  • При использовании соглашений о вызовах register и pascal параметры в подпрограмму передаются слева направо, т.е. самый левый параметр передаётся первым, а самый правый – последним. При использовании соглашений о вызовах cdecl, stdcall и safecall параметры в подпрограмму передаются справа налево.
  • При использовании всех соглашений о вызовах кроме cdecl подпрограмма удаляет параметры из стека при возврате из подпрограммы. При использовании соглашения о вызовах cdecl вызывающая программа удаляет параметры из стека после возврата управления.
  • При использовании соглашения о вызовах register до трёх параметров могут быть переданы через регистры процессора. При использовании остальных соглашений о вызовах все параметры передаются через стек.
  • При использовании соглашения о вызовах safecall может возбуждаться исключение ‘firewall’. На платформе Win32 это исключение позволяет передавать уведомления об ошибках между процессами, взаимодействующими через COM-модель.

Директива | Порядок передачи параметров | Очистка стека | Использование регистров
register | Слева направо | Подпрограммой | Да
pascal | Слева направо | Подпрограммой | Нет
cdecl | Справа налево | Вызывающей программой | Нет
stdcall | Справа налево | Подпрограммой | Нет
safecall | Справа налево | Подпрограммой | Нет

Используемое по умолчанию соглашение о вызовах register является наиболее эффективным, т.к. во многих случаях позволяет избежать создания стекового фрейма для подпрограммы. Соглашение о вызовах cdecl бывает полезно при импорте внешних подпрограмм из динамических библиотек, написанных на языке C++. Соглашения о вызовах stdcall и safecall обычно используются для внешних подпрограмм. Соглашение о вызовах pascal поддерживается для обратной совместимости.

Директивы near, far и export описывают соглашения о вызовах, использовавшихся в 16-битных приложениях. Они не имеют эффекта на платформе Win32 и поддерживаются только для обратной совместимости.

2. Разработка dll-библиотеки

2.1. Структура исходного кода dll-библиотеки

Исходный код dll-библиотеки имеет схожую структуру во всех языках программирования. Библиотека должна содержать:

  • необязательную часть кода, которая отвечает за инициализацию и очистку библиотеки;
  • набор подпрограмм библиотеки;
  • явное указание, какие подпрограммы должны экспортироваться из библиотеки.

Инициализирующая часть кода оформляется в виде процедуры, которая получает ряд параметров, один из которых указывает на причину обращения. Этот параметр может принимать одно из следующих значений.

Значение параметра | Смысл
DLL_PROCESS_ATTACH | Указывает, что библиотека подключена к вызывающему процессу
DLL_PROCESS_DETACH | Указывает, что библиотека отсоединена от вызывающего процесса
DLL_THREAD_ATTACH | Указывает, что процесс создаёт новый поток
DLL_THREAD_DETACH | Указывает, что процесс завершает поток

Процедура должна возвращать значение истина в случае успешного завершения, и значение ложь при возникновении ошибки. В этом случае дальнейшие действия прекращаются.

2.2. Разработка dll-библиотеки на языке С++

2.2.1. Принципы разработки

Для создания dll-библиотеки в приложении Microsoft Visual Studio необходимо создать новый проект Win32 и после задания имени проекта в окне параметров приложения выбрать тип приложения DLL. Если остальные параметры оставить без изменений, Microsoft Visual Studio автоматически сгенерирует шаблон библиотеки. Если же создать пустой проект (empty project), то инициализирующую часть кода придётся писать самостоятельно. В языке С++ основной функцией dll-библиотеки является функция, которую обычно называют 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; }

Функция DllMain получает три параметра. Первый является идентификатором библиотеки. Второй определяет причину вызова функции DllMain (см. раздел 2.1). Третий параметр зарезервирован для внутреннего использования в Windows.

Функции, которые будут экспортироваться из dll-библиотеки, должны быть объявлены со спецификацией extern "C" __declspec(dllexport), которая указывает, что функция должна быть доступна приложению, использующему библиотеку, и что имена должны преобразовываться по правилам языка С.
extern "C" __declspec(dllexport) <тип> <имя> (<параметры>) { … }

2.2.2. Пример dll-библиотеки на языке С++

// dllmain.cpp: Defines the entry point for the DLL application

#include <windows.h>

#include <cstdio>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)

{ switch (ul_reason_for_call)

{ case DLL_PROCESS_ATTACH:

// Будьте внимательны!!! Функция printf используется исключительно для примера. // Библиотека будет работать только в консольном приложении. // В обычное приложение под Windows подключать эту библиотеку нельзя!

printf("Hello, I am an example library!\n");

break;

case DLL_THREAD_ATTACH: break;

case DLL_THREAD_DETACH: break;

case DLL_PROCESS_DETACH: printf("Goodbye. Example library\n");

break; }

return TRUE; }

// ExampleDll.cpp: Defines the exported functions for the DLL application

extern "C" __declspec(dllexport) void ExampleFunction(char *source, char *dest)

{

int i; for (i = 0; source[i]; i++) dest[i] = source[i]; dest[i] = '\0';

}

После успешной компиляции вы получите файлы .dll и .lib. Файл с расширением lib – это так называемая библиотека импорта, которая требуется при статическом подключении dll-библиотеки.

2.3. Разработка dll-библиотеки на языке Паскаль

2.3.1. Принципы разработки

Файл с исходным кодом библиотеки аналогичен файлу с исходным кодом программы, за исключением того, что заголовок библиотеки начинается со слова library. Таким образом, раздел объявлений библиотеки содержит объявления процедур и функций, а также констант, типов и переменных, необходимых для работы процедур и функций. Раздел операторов, в обычной программе содержащий основной код программы, в dll-библиотеке содержит код инициализации библиотеки.

Для того чтобы подпрограмма из dll-библиотеки была импортирована в другое приложение, она должна быть явно указана в операторе exports. Этот оператор имеет следующий синтаксис:
exports <элемент1>, …, <элементN>;

Каждый элемент представляет собой имя процедуры или функции (которая должна быть определена до оператора exports), за которым должен следовать список параметров (для совместно используемых процедур и функций), и может следовать необязательная спецификация name.

Спецификация name состоит из директивы name и следующей за ней строкой. Если элемент не имеет спецификации name, то процедура или функция экспортируется под своим исходным именем с тем же написанием и регистром. Спецификация name позволяет экспортировать процедуру или функцию под другим именем. Совместно используемые процедуры и функции желательно экспортировать под разными именами.
exports Divide(x, y: Integer) name 'Divide_Ints', Divide(x, y: Real) name 'Divide_Reals';

Оператор exports может встречаться несколько раз в разделе объявлений.

Для экспортируемых процедур желательно использовать соглашение о вызовах stdcall или cdecl, если предполагается импортировать подпрограммы в программы, написанные на языке C++.

Библиотека может быть построена из нескольких модулей – в этом случае код библиотеки будет содержать только операторы usesи exports и код инициализации библиотеки. Оператор exports можно поместить в раздел описания интерфейса или раздел реализации модуля. Библиотека, подключающая такой модуль, автоматически экспортирует подпрограммы, перечисленные в операторах exportsмодуля.

Функция IsLibrary, объявленная в модуле System, позволяет определить, выполняется ли код в приложении или в библиотеке. Функция возвращает значение истина при выполнении в коде библиотеки, и ложь при выполнении в коде приложения. Это может быть полезно в модуле, поскольку разработчик модуля не знает, куда именно будет подключён модуль.

Раздел операторов в блоке dll-библиотеки содержит код инициализации библиотеки. Эти операторы выполняются один раз при каждой загрузке библиотеки. Они обычно выполняют такие действия как регистрация классов окон и инициализация переменных.

Код инициализации библиотеки может сигнализировать об ошибке, присваивая переменной ExitCode ненулевое значение. Переменная ExitCode определена в модуле System и по умолчанию имеет значение 0. Если код инициализации библиотеки присваивает этой переменной другое значение, библиотека выгружается из памяти и вызывающему приложению передаётся уведомление об ошибке.

Код инициализации библиотеки может также устанавливать процедуру точки входа, которая выполняется при загрузке и выгрузке библиотеки. Переменная DLLProc позволяет библиотеке отслеживать вызовы операционной системы. Обычно эта возможность необходима только библиотекам, которые поддерживают многопоточность. В противном случае лучше использовать разделы инициализации и завершения модуля.

Для отслеживания вызовов операционной системы надо определить процедуру обратного вызова, получающую один параметр целого типа. Адрес этой процедуры необходимо присвоить переменной DLLProc. Когда процедура вызывается, ей через параметр передаётся одно из значений, определённых в разделе 2.1. В теле процедуры можно указать необходимые действия в зависимости от значения, переданного в процедуру.

library Test; uses SysUtils, Windows; var SaveDllProc: procedure(reason: integer); procedure LibraryProc(reason: integer); begin case reason of DLL_PROCESS_ATTACH: writeln('PROCESS_ATTACH'); DLL_PROCESS_DETACH: writeln('PROCESS_DETACH'); end; if @SaveDllProc <> nil then SaveDllProc(reason); end; begin SaveDllProc := DllProc; DllProc := @LibraryProc; end.

2.3.2. Пример dll-библиотеки на языке Паскаль

library
InputOutput; uses SysUtils, Classes, Windows; const NArrayMax =
100; type IntArray = array [1..NArrayMax] of integer;
RealArray = array [1..NArrayMax] of real; var SaveDllProc:
procedure(reason: integer); procedure LibraryProc(reason:
integer); begin case reason of { writeln опять-таки работает только в
консольном приложении } DLL_PROCESS_ATTACH: writeln('PROCESS_ATTACH');
DLL_PROCESS_DETACH: writeln('PROCESS_DETACH'); end; if
@SaveDllProc <> nil then SaveDllProc(reason); end;
{ Процедура ввода целочисленного одномерного
массива из файла } procedure Get(var x: IntArray;
var n: integer; var f: TextFile); overload; stdcall;
var i: integer; begin readln(f, n); for i := 1 to n
do read(f, x[i]); readln(f); end; { Процедура ввода вещественного одномерного массива
из файла } procedure Get(var x: RealArray; var n:
integer; var f: TextFile); overload; stdcall; var i:
integer; begin readln(f, n); for i := 1 to n do
read(f, x[i]); readln(f); end; {
Процедура вывода целочисленного одномерного массива в файл }
procedure Put(const x: IntArray; n: integer; name: string;
var f: TextFile); stdcall; var i: integer; begin
writeln(f, 'The array ', name, ' of ', n:2, ' elements');
for i := 1 to n do write(f, x[i]:8); writeln(f);
writeln(f); end; { Указываем
экспортируемые подпрограммы. Имя Get является совместно используемым, поэтому
процедуры экспортируются под разными именами. } exports
Get(var x: IntArray; var n: integer; var f: TextFile)
name 'GetIntArray', Get(var x: RealArray; var n: integer;
var f: TextFile) name 'GetRealArray', Put; begin
SaveDllProc := DllProc; DllProc := @LibraryProc; end.

2.4. Разработка dll-библиотеки на языке Ассемблер

2.4.1. Принципы разработки

Исходный код на языке Ассемблер для dll-библиотеки должен содержать набор процедур, одна из которых – инициализирующая – схожа по структуре с инициализирующей функцией на языке С++. Процедура должна получать такие же три параметра и помещать в регистр EAX значение -1 (истина) для продолжения работы и значение 0 (ложь) для завершения работы. Остальные процедуры либо предназначены для экспорта из библиотеки, либо являются вспомогательными.

Однако для указания экспортируемых процедур надо создать так называемый файл установок модуля, имеющий расширение def. В этом файле указывается имя и описание библиотеки, а также имена и другие параметры экспортируемых процедур.
LIBRARY MyDLL DESCRIPTION 'MyDLL - пример DLL-библиотеки' EXPORTS MyFunction

Для каждой процедуры можно указать порядковый номер (после знака @). Этот номер может быть использован для вызова процедуры при динамическом подключении библиотеки (см. раздел 3.3). На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако способ, которым он это делает, непредсказуем, если не присвоить эти номера явно. В строке экспорта можно также использовать параметр NONAME. Он запрещает компилятору включать имя процедуры в таблицу экспортирования DLL. Иногда это позволяет сэкономить много места в dll-файле. Приложения, использующие библиотеку импортирования для неявного подключения, не «заметят» разницы, поскольку при неявном подключении порядковые номера используются автоматически. Приложениям, загружающим библиотеку динамически, потребуется передавать в функцию GetProcAddress порядковый номер, а не имя процедуры.
LIBRARY MyDLL DESCRIPTION 'MyDLL - пример DLL-библиотеки' EXPORTS MyFunction @1 NONAME

После создания исходного кода и файла установок модуля нужно обычным способом скомпилировать объектный файл, а компоновщик вызвать с дополнительными параметрами – задать имя файла установок модуля и указать, что необходимо построить именно библиотеку. При вызове компоновщика из директории, в которой находятся файлы .def и .obj команда вызова будет иметь примерно такой вид:
d:\masm32\bin\link /dll /subsystem:windows /def:MyDLL.def MyDLL.obj

При отсутствии ошибок компоновщик создаст файлы .dll и .lib. Файл с расширением lib – это так называемая библиотека импорта, которая требуется при статическом подключении dll-библиотеки.

2.4.2. Пример dll-библиотеки на языке Ассемблер

; Файл ExampleDll.asm .686 .model flat, c option casemap:none .code ;--------------------------------------------------------------------------- ; Инициализирующая процедура библиотеки. Имя процедуры может быть любым ;--------------------------------------------------------------------------- DllMain proc hInstDLL:DWORD, reason:DWORD, reserved1:DWORD mov eax, -1 ret DllMain endp ;---------------------------------------------------------------------------- ; Процедура, экспортируемая из библиотеки ;---------------------------------------------------------------------------- ExampleProc proc push ebp mov ebp, esp push esi push edi mov esi, [ebp + 8] mov edi, [ebp + 12] xor eax, eax L1: mov al, [esi] mov [edi], al inc esi inc edi test eax, eax jnz L1 pop edi pop esi mov esp, ebp pop ebp ret ExampleProc endp End DllMain ; Файл ExampleDll.def LIBRARY ExampleDll EXPORTS ExampleProc

3. Использование dll-библиотеки

Прежде чем вызывать подпрограммы, включённые в dll-библиотеку, их надо импортировать в приложение. Это можно сделать двумя способами – путём объявления внешней процедуры или функции или динамически с помощью Win32 API функций. Но в любом случае подпрограммы из dll-библиотеки будут импортированы только во время работы приложения.

3.1. Поиск dll-библиотек

Чтобы загрузить dll-библиотеку, операционная система должна найти её. Поиск осуществляется в следующих местах:

  • директория, из которой загружено приложение, требующее dll-библиотеку;
  • текущая директория;
  • системная директория (обычно C:\Windows\System32);
  • системная директория для 16-битных приложений (обычно C:\Windows\System);
  • Windows-директория;
  • директории, указанные в переменной окружения PATH.

Последовательность просмотра директорий может быть разной, это зависит от настроек, но, в общем, просматриваются перечисленные директории до нахождения требуемой библиотеки. Если ни в одной из перечисленных директорий библиотека не найдена, приложение получает уведомление об ошибке.

3.2. Статическое подключение

Самый простой способ импортировать процедуру или функцию – тем или иным способом объявить её как внешнюю. Необходимо также указать имя подключаемой dll-библиотеки.

3.2.1. Статическое подключение dll-библиотеки в программе на языке С++

Для статического подключения dll-библиотеки в программе на языке С++ необходимо скопировать файл .lib в директорию с исходным кодом приложения и указать имя этого файла в настройках компоновщика – Project > Properties > Configuration Properties > Linker > Input > Additional Dependencies. Кроме того, поскольку функции должны иметь прототипы, потребуется заголовочный файл с прототипами функций, который необходимо вставить с помощью директивы препроцессора #include в файлы с исходным кодом, в которых предполагается использование функций dll-библиотеки. Заголовочный файл поставляется разработчиком библиотеки. Функции должны быть объявлены со спецификацией extern "C" __declspec(dllimport), которая указывает, что функция будет импортирована из внешнего модуля.

// Использование dll-библиотеки. Пример библиотеки см. в разделе 2.2.2 // ExampleDll.h: Defines the imported functions extern "C" __declspec(dllimport) void ExampleFunction(char *source, char *dest); // DllTest.cpp #include "StringDll.h" #include <cstdio> void main() { char str[256], res[256]; printf("Input a string\n"); gets(str); ExampleFunction(str, res); printf("New string - '%s'\n\n", res); }

3.2.2. Статическое подключение dll-библиотеки в программе на языке Паскаль

В языке Паскаль и объявление внешней процедуры, и указание подключаемой библиотеки осуществляется с помощью директивы external:
procedure <имя> (<список параметров>); external <строка, задающая имя библиотеки>; function <имя> (<список параметров>): <тип результата>; external <строка, задающая имя библиотеки>;

Если включить в код приложения подобное объявления, библиотека, имя которой задано после ключевого слова external, будет загружаться при запуске приложения. Имя импортируемой процедуры или функции будет означать одну и ту же подпрограмму из одной и той библиотеки всё время выполнения приложения.

// Использование dll-библиотеки. Пример библиотеки см. в разделе 2.3.2

program LibraryTest1; {$APPTYPE CONSOLE} {$I-}

uses SysUtils;

const NArrayMax = 100;

type IntArray = array [1..NArrayMax] of integer;

procedure GetIntArray(var x: IntArray; var n: integer; var f: TextFile);

stdcall; external 'InputOutput.dll';

procedure Put(const x: IntArray; n: integer; name: string; var f: TextFile);

stdcall; external 'InputOutput.dll';

var a: IntArray; n: integer; f: TextFile;

begin

if ParamCount < 2 then begin writeln('There are no enough parameters');  readln;

exit;

end; AssignFile(f, ParamStr(1)); Reset(f); if IOResult <> 0 then begin writeln('It is not possible to open file ''', ParamStr(1), ''' for reading'); readln; exit; end; GetIntArray(a, n, f); CloseFile(f); AssignFile(f, ParamStr(2)); Rewrite(f); if IOResult <> 0 then begin writeln('It is not possible to open file ''', ParamStr(2), ''' for writing'); writeln('Output is made to the screen. Press ENTER'); readln; AssignFile(f, ''); Rewrite(f); end; Put(a, n, 'A', f); CloseFile(f); readln; end.

3.2.3. Статическое подключение dll-библиотеки в программе на языке Ассемблер

Для статического подключения dll-библиотеки в программе на языке Ассемблер потребуется файл .lib, а также файл с расширением inc, который аналогичен заголовочному файлу языка С++. В исходный код программы нужно включить файл .inc с помощью директивы include и файл .lib с помощью директивы includelib. После этого можно вызывать процедуры dll-библиотеки.

; Использование dll-библиотеки. Пример библиотеки см. в разделе 2.4.2 ; Файл ExampleDll.inc ; Файл содержит прототип процедуры, экспортируемой из библиотеки, что указывает ключевое слово PROTO. ; Ключевое слово CDECL указывает используемое соглашение о вызовах. Далее описываются параметры, которые необходимо передать в процедуру. ; В связи со спецификой передачи параметров в программах на языке Ассемблер это описание является необязательным. ExampleProc PROTO CDECL :DWORD, :DWORD ; Файл DllTest.asm .686 .model flat, c option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include ExampleDll.inc includelib \masm32\lib\kernel32.lib includelib ExampleDll.lib .data source db 'I am a testing string',0 dest db 25 dup(0) .code program: push offset dest push offset source call ExampleProc add esp, 8 push 0 call ExitProcess end program

3.3. Динамическое подключение

Доступ к подпрограммам dll-библиотеки можно получить с помощью Win32 API функций LoadLibrary, FreeLibrary и GetProcAddress. При импорте подпрограмм данным способом, библиотека загружается только при вызове функции LoadLibrary и выгружается при вызове функции FreeLibrary. Это позволяет уменьшать требуемое количество памяти и запускать приложение, даже если какие-то библиотеки отсутствуют на компьютере.

Функция LoadLibrary ищет и загружает dll-библиотеку по переданному ей имени. Поиск осуществляется в тех же директориях, что и при статическом подключении (см. раздел 3.1). Функция возвращает идентификатор библиотеки, если библиотека была найдена, и значение 0 в противном случае.

Функция GetProcAddress осуществляет поиск нужной процедуры в dll-библиотеке. Функция получает идентификатор библиотеки и имя или номер процедуры и возвращает указатель на процедуру, если она была найдена, и 0 в противном случае.

Функция FreeLibrary освобождает dll-библиотеку. Однако библиотека может не выгружаться из памяти, если она используется другими приложениями.

При динамическом подключении dll-библиотеки компилятор не может проверить типы и количество передаваемых параметров. Поэтому программист сам должен следить за тем, чтобы процедура получила корректный набор параметров.

Динамическое подключение dll-библиотек более трудоёмко, чем статическое, т.к. нужно самостоятельно вызывать все необходимые функции и проверять возвращаемые ими значения. Кроме того, при динамическом подключении генерируется чуть более объёмный код. Но динамическое подключение является более гибким механизмом. При статическом подключении в случае отсутствия необходимой dll-библиотеки приложение просто не запустится. При динамическом подключении в случае отсутствия dll-библиотеки программист может предпринять какие-либо действия, например, попытаться загрузить другую библиотеку или позволить пользователю работать с теми функциями приложения, которые определены не в отсутствующей библиотеке.

3.3.1. Динамическое подключение dll-библиотеки в программе на языке С++

Для использования функций динамического подключения dll-библиотек и соответствующих типов нужно включить заголовочный файл windows.h.

// Использование dll-библиотеки. Пример библиотеки см. в разделе 2.2.2

#include <cstdio>

#include <windows.h>

 typedef void (*LibraryFunction)(char *, char *); // Объявляем тип для указателя на библиотечную функцию

void main()

{

char str[256], res[256]; HINSTANCE hLib; // Объявляем идентификатор библиотеки

LibraryFunction f; // Объявляем указатель на библиотечную функцию

hLib = LoadLibrary(TEXT("ExampleDll.dll"));

// Загружаем библиотеку

if (hLib == NULL) // Проверяем результат загрузки библиотеки

{

printf("Unable to load the library 'ExampleDll.dll'!\n");

return;

}

// Получаем указатель на функцию ExampleFunction и преобразуем его к нужному типу

f = (LibraryFunction)GetProcAddress(hLib, "ExampleFunction"); if (!f) // Проверяем полученный указатель

printf("Unable to find the function 'ExampleFunction'!\n\n");

else

{

printf("Input a string\n"); gets(str); f(str, res); printf("'%s'\n\n", res);

}

FreeLibrary(hLib);

// Освобождаем библиотеку

}

3.3.2. Динамическое подключение dll-библиотеки в программе на языке Паскаль

Функции для динамического подключения dll-библиотек определены в модуле Windows.

// Использование dll-библиотеки. Пример библиотеки см. в разделе 2.3.2

program LibraryTest2; {$APPTYPE CONSOLE} {$I-}

uses SysUtils, Windows;

const NArrayMax = 100;

type IntArray = array [1..NArrayMax] of integer;

TProcGet = procedure (var x: IntArray; var n: integer; var f: TextFile);

stdcall;
TProcPut = procedure (const x: IntArray; n: integer; name:
string; var f: TextFile);

stdcall; var a: IntArray; n: integer; f: TextFile; handle: integer; ProcGet: TProcGet; ProcPut: TProcPut;

begin { Загружаем dll-библиотеку }

handle := LoadLibrary('InputOutput.dll');

{ Проверяем результат загрузки библиотеки }

if handle = 0 then begin writeln('It is not possible to load the library ''InputOutput.dll'''); readln; exit; end;

if ParamCount < 2 then begin writeln('There are no enough parameters'); readln; exit; end; AssignFile(f, ParamStr(1)); Reset(f); if IOResult <> 0 then begin writeln('It is not possible to open file ''', ParamStr(1), ''' for reading'); readln;

{ Освобождаем библиотеку }

FreeLibrary(handle); exit; end;

{ Загружаем адрес требуемой процедуры, handle – идентификатор, полученный при загрузке библиотеки }

@ProcGet := GetProcAddress(handle, 'GetIntArray');

{ Проверяем результат загрузки адреса процедуры }

if
@ProcGet = nil then begin writeln('It is not possible to
get the procedure ''GetIntArray'' from the library ''InputOutput.dll''');
readln;

{ Освобождаем библиотеку }

 FreeLibrary(handle); exit; end; ProcGet(a, n, f); CloseFile(f); AssignFile(f, ParamStr(2)); Rewrite(f); if IOResult <> 0 then begin writeln('It is not possible to open file ''', ParamStr(2), ''' for writing'); writeln('Output is made to the screen. Press ENTER'); readln; AssignFile(f, ''); Rewrite(f); end;

{ Загружаем адрес другой процедуры }

@ProcPut := GetProcAddress(handle, 'Put');

{ Проверяем результат загрузки адреса процедуры }

if @ProcGet = nil then begin writeln('It is not possible to get the procedure ''Put'' from the library ''InputOutput.dll'''); readln;

{ Освобождаем библиотеку }

FreeLibrary(handle); exit; end; ProcPut(a, n, 'A', f); CloseFile(f);

{ Освобождаем библиотеку }

FreeLibrary(handle); readln; end.

3.3.3. Динамическое подключение dll-библиотеки в программе на языке Ассемблер

Для динамического подключения используются те же функции с теми же параметрами. Вызов функций осуществляется с учётом специфики языка Ассемблер. Для вызова функций Win32 API необходимо включить файл kernel32.inc и подключить библиотеку импорта kernel32.lib.

; Использование dll-библиотеки. Пример библиотеки см. в разделе 2.4.2 .686 .model flat, c option casemap:none include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data source db 'I am a testing string',0 dest db 25 dup(0) .data? hLib dd ? fAddr dd ? .const libName db 'ExampleDll.dll',0 fName db 'ExampleProc',0 lnf db 'Library ''ExampleDll.dll'' not found',13,10 fnf db 'Function ''ExampleProc'' not found',13,10 .code program: ; Получаем идентификатор вывода push -11 call GetStdHandle mov hStdOut, eax ; Получаем идентификатор библиотеки push offset libName call LoadLibrary ; Проверяем полученный идентификатор test eax, eax jz L0 mov hLib, eax ; Получаем указатель на функцию push offset fName push hLib call GetProcAddress ; Проверяем полученный указатель test eax, eax jz L1 ; Вызываем функцию push offset dest push offset source mov fAddr, eax call [fAddr] add esp, 8 ; Освобождаем библиотеку push hLib call FreeLibrary push 0 call ExitProcess L0: push 0 push 0 push 36d push offset lnf push hStdOut call WriteConsoleA ; Задержка push 1000h call Sleep push 0 call ExitProcess L1: push 0 push 0 push 34d push offset fnf push hStdOut call WriteConsoleA ; Задержка push 1000h call Sleep ; Освобождаем библиотеку push hLib call FreeLibrary push 0 call ExitProcess end program

4. Использование dll-библиотеки, реализованной на другом языке программирования

До сих пор мы рассматривали случаи, когда dll-библиотека и вызывающая программа написаны на одном и том же языке программирования. Но иногда возникает необходимость подключить в программу библиотеку, реализованную на другом языке программирования. Никаких принципиальных сложностей тут нет. Надо только следить, чтобы при реализации и вызове процедур использовались одинаковые соглашения о вызовах, а также за соответствием формальных и фактических параметров. Однако в этом случае соответствие параметров будет не формальным. Программист должен понимать, какие типы одного языка соответствуют тем или иным типам другого языка. С простыми типам, например, целыми и вещественными, проблем возникать не должно. А вот сложные, высокоуровневые типы не всегда будут соответствовать друг другу. Например, строки в языках С++ и Паскаль реализуются по-разному.

4.1. Использование dll-библиотеки, реализованной на языке Ассемблер, в программе на языке С++

Язык Ассемблер оперирует низкоуровневыми объектами, поэтому никаких несоответствий в типах параметров возникать не должно. Единственное формальное требование, которое можно предъявить в этом случае, – соответствие размеров передаваемых данных. Если процедура, написанная на языке Ассемблер, ожидает 4 байта, то можно передать и целое число, и указатель, и даже вещественное число типа float. Так что импортируемую функцию в программе на языке С++ можно, в принципе, определить разными способами, причём большинство из них окажется работоспособными. Например, если функция ожидает указатель, можно объявить параметр с типом int. Если после этого передать в функцию указатель, преобразованный к типу int, программа будет работать без ошибок.

Однако нужно понимать, что каждый передаваемый в процедуру параметр используется определённым образом. И если в процедуру, ожидающую указатель, передать значение, не являющееся адресом реального объекта программы, это приведёт к возникновению ошибок и некорректной работе программы. Таким образом, несмотря на отсутствие формальных требований к типам параметров, смысловое соответствие типов должно сохраняться.

Поскольку библиотеку планируется использовать в программе на языке С++, будем считать, что параметры передаются в обратном порядке, и что вызывающая программа сама освободит стек (т.е. используем команду языка ассемблера RET без параметров).

; Файл AsmDll.asm. Кроме этого файла необходимо также создать файл .def и скомпилировать библиотеку, как указано в разделе 2.4.1. .686 .model flat, c option casemap:none .code DllMain proc, hInstDLL:DWORD, reason:DWORD, reserved1:DWORD mov eax, -1 ret DllMain endp ;----------------------------------------------------------------------------------------------------- ; Процедура, считающая в одномерном целочисленном массиве сумму элементов, кратных заданному числу. ; Процедура получает указатель на начало массива, количество элементов массива и число. ; Результат возвращается через регистр EAX. ;----------------------------------------------------------------------------------------------------- SumOfMultiples proc push ebp mov ebp, esp mov ecx, [ebp + 12] cmp ecx, 0 jle E1 push esi push edi mov esi, [ebp + 8] mov ebx, [ebp + 16] xor edi, edi L0: mov eax, [esi] cdq idiv ebx test edx, edx jnz M1 mov eax, [esi] add edi, eax M1: add esi, 4 dec ecx ja L0 mov eax, edi pop edi pop esi mov esp, ebp pop ebp ret E1: xor eax, eax mov esp, ebp pop ebp ret SumOfMultiples endp ;--------------------------------------------------------------------------------------------- ; Процедура копирования строки. Процедура получает адрес исходной строки и адрес результата. ;--------------------------------------------------------------------------------------------- StrCpy proc push ebp mov ebp, esp push esi push edi mov esi, [ebp + 8] mov edi, [ebp + 12] xor eax, eax L1: mov al, [esi] mov [edi], al inc esi inc edi test eax, eax jnz L1 pop edi pop esi mov esp, ebp pop ebp ret StrCpy endp End DllMain // Файл DllTest.cpp. В настройки проекта добавляется файл AsmDll.lib, как указано в разделе 3.2.1 // Объявляем прототипы импортируемых функций в соответствии с назначением передаваемых параметров // Функции нужно объявить с директивой extern "С", т.к. ассемблер генерирует имена в стиле языка С extern "C" __declspec(dllimport) int SumOfMultiples(int x[], int n, int y); extern "C" __declspec(dllimport) void StrCpy(const char *source, char *dest); #include <cstdio> void main() { int a[10] = {1, 2, 3, 5, 7, 10, 15, -6, 8, -21}; char str[100]; printf("%% 2 ( 14) - %3d\n", SumOfMultiples(a, 10, 2)); printf("%% 3 ( -9) - %3d\n", SumOfMultiples(a, 10, 3)); printf("%% 5 ( 30) - %3d\n", SumOfMultiples(a, 10, 5)); StrCpy("Скопируй меня!", str); printf("%s\n", str); }

4.2. Использование dll-библиотеки, реализованной на языке Паскаль, в программе на языке С++

Базовые типы языков С++ и Паскаль после компиляции (в машинных кодах) реализуются одинаково. Что касается массивов, то массивы языка С++ совместимы со статическими и динамическими массивами языка Паскаль, но не с открытыми массивами.

Соглашение о вызовах, используемое в языке Паскаль по умолчанию, отличается от соглашений о вызовах языка С++. Поэтому для импортируемых процедур надо явно указать соглашение о вызовах cdecl или stdcall.

Кроме того, компиляторы языка Паскаль не генерируют файл .lib, поэтому статическое подключение dll-библиотеки не возможно.

// Файл DllForC.pas. Используем соглашение о вызовах stdcall library DllForC; type IntArray = array of integer; RealArray = array of real; // Функция, считающая в вещественном массиве сумму элементов, больших заданного числа function Sum(x: RealArray; n: integer; y: real): real; stdcall; var i: integer; begin result := 0; for i := 0 to n - 1 do if x[i] > y then result := result + x[i]; end; // Функция, считающая в целочисленном массиве количество элементов, кратных заданному числу function Count(x: IntArray; n: integer; y: integer): integer; stdcall; var i: integer; begin result := 0; for i := 0 to n - 1 do if x[i] mod y = 0 then result := result + 1; end; // Функция, проверяющая, есть ли в целочисленном массиве элементы, равные заданному числу function Check(x: IntArray; n: integer; y: integer): boolean; stdcall; var i: integer; begin Check := false; for i := 0 to n - 1 do if x[i] = y then begin Check := true; break; end; end; exports Sum, Count, Check; begin end. // Файл DllTest.cpp #include <fstream> #include <iostream> #include <locale.h> #include <windows.h> using namespace std; const int nmax = 100; // Типы указателей для функций dll-библиотеки typedef double (_stdcall *F1)(double *, int, double); typedef int (_stdcall *F2)(int *, int, int); typedef bool (_stdcall *F3)(int *, int, int); // Шаблон функции для ввода массивов разного типа template <typename T> int ArrayInput(T x[], int *n, T *y, char *fname); void main(int argc, char *argv[]) { double a[nmax]; double ya; int b[nmax], c[nmax]; int na, nb, nc; int yb, yc; HINSTANCE hLib; F1 f1; F2 f2; F3 f3; setlocale(LC_ALL, "rus"); if (argc < 4) { printf("Недостаточно параметров!\n"); return; } if (!ArrayInput(a, &na, &ya, argv[1])) return; if (!ArrayInput(b, &nb, &yb, argv[2])) return; if (!ArrayInput(c, &nc, &yc, argv[3])) return; hLib = LoadLibrary(TEXT("DllForC.dll")); if (hLib == NULL) { printf("Unable to load the library 'DllForC.dll'!\n"); return; } f1 = (F1)GetProcAddress(hLib, "Sum"); if (!f1) printf("Unable to find the function 'Sum'!\n\n"); else printf("Сумма элементов, больших %8.3lf = %8.3lf\n", ya, f1(a, na, ya)); f2 = (F2)GetProcAddress(hLib, "Count"); if (!f2) printf("Unable to find the function 'Count'!\n\n"); else printf("Количество элементов, кратных %3d = %3d\n", yb, f2(b, nb, yb)); f3 = (F3)GetProcAddress(hLib, "Check"); if (!f3) printf("Unable to find the function 'Check'!\n\n"); else if (f3(c, nc, yc)) printf("Есть элементы, равные %3d\n", yc); else printf("Нет элементов, равных %3d\n", yc); FreeLibrary(hLib); } template <typename T> int ArrayInput(T x[], int *n, T *y, char *fname) { ifstream f(fname); if (!f.is_open()) { cout << "Невозможно открыть файл '" << fname << "'\n"; return 0; } f >> *n; if (f.fail()) { cout << "Ошибка чтения из файла '" << fname << "'\n"; f.close(); return 0; } for (int i = 0; i < *n; i++) { f >> x[i]; if (f.fail()) { cout << "Ошибка чтения из файла '" << fname << "'\n"; f.close(); return 0; } } f >> *y; if (f.fail()) { cout << "Ошибка чтения из файла '" << fname << "'\n"; f.close(); return 0; } f.close(); return 1; }

4.3. Использование dll-библиотеки, реализованной на языке С++, в программе на языке Паскаль

При использовании в программе на языке Паскаль dll-библиотеки, реализованной на языке С++, также необходимо следить за фактическим соответствием типов и размеров параметров и за используемым соглашением о вызовах. Кроме того, функции dll-библиотеки нужно объявлять с директивой extern "C", т.к. язык С++ использует сложный алгоритм модификации имён, не совместимый с языком Паскаль.

// dllmain.cpp: Defines the entry point for the DLL application.

#include <windows.h> #include <cstdio> BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)

{ switch (ul_reason_for_call)

  { case DLL_PROCESS_ATTACH:

printf("Hello, I am a C library!\n");

break;

case DLL_PROCESS_DETACH:

printf("Goodbye. C library\n");

break; case DLL_THREAD_ATTACH:

case DLL_THREAD_DETACH:

 break;

}

 return TRUE;

} // DllForPascal.cpp: Defines the exported functions for the DLL application.

// Функция, считающая в вещественном массиве сумму элементов, больших заданного числа

extern "C" __declspec(dllexport) double Sum(double x[], int n, double y)

{

double s = 0;

for (int i = 0; i < n; i++)

if (x[i] > y)

s += x[i];

return s;

}

// Функция, считающая в целочисленном массиве количество элементов, кратных заданному числу

extern "C" __declspec(dllexport) int Count(int x[], int n, int y)

{ int k = 0;

for (int i = 0; i < n; i++)

if (x[i] % y == 0)

k++;

return k;

}

// Функция, проверяющая, есть ли в целочисленном массиве элементы, равные заданному числу

extern "C" __declspec(dllexport) bool Check(int x[], int n, int y)

{ for (int i = 0; i < n; i++)

 if (x[i] == y)

  return true;

return false;

}

program DllTest;

{$APPTYPE CONSOLE}

 uses SysUtils;

function Sum(x: RealArray; n: integer; y: real): real; cdecl; external 'DllForPascal.dll';

function Count(x: IntArray; n: integer; y: integer): integer; cdecl; external 'DllForPascal.dll';

function Check(x: IntArray; n: integer; y: integer): boolean; cdecl; external 'DllForPascal.dll';

var

a: RealArray;

b, c: IntArray;

 na, nb, nc: integer;

 ya: real; yb, yc: integer;

f: TextFile;

begin

// Ввод массивов

...

writeln('Sum of elements > ', ya:6:2, ' = ', Sum(a, na, ya):6:2);

writeln('Count of elements multiple of ', yb, ' = ', Count(b, nb, yb));

 if Check(c, nc, yc) then

  writeln('There are elements = ', yc)

 else

writeln('There is no element = ', yc);

end.


Пошаговое руководство. Создание и использование библиотеки DLL (C++)

Пошаговое руководство. Создание и использование библиотеки DLL (C++)

В этом пошаговом руководстве описывается создание библиотеки динамической компоновки (DLL) для использования с приложением на C++. Библиотеки являются хорошим способом повторного использования кода. Вместо того чтобы каждый раз реализовывать одни и те же подпрограммы в каждом создаваемом приложении, их можно создать единожды и затем ссылаться на них из других приложений. Поместив код в библиотеку DLL, вы сэкономите место в каждом приложении, которое ссылается на этот код, а также сможете обновлять DLL без перекомпиляции всех приложений.

В этом пошаговом руководстве рассматриваются следующие задачи:

  • создание проекта DLL;

  • добавление класса в DLL;

  • создание консольного приложения, использующего динамическую компоновку времени загрузки для ссылки на библиотеку DLL;

  • использование функциональности из класса в приложении;

  • запуск приложения.

В этом пошаговом руководстве будет создана библиотека DLL, которая может вызываться только из приложений, использующих соглашения о вызове C++.

Обязательные компоненты

Это руководство предполагает знание основ языка C++.

Создание проекта библиотеки динамической компоновки (DLL)

  1. В строке меню выберите Файл, Создать, Проект.

  2. В левой области диалогового она Создать проект разверните Установленные, Шаблоны, Visual C++ и затем выберите Win32.

  3. В центральной области выберите Консольное приложение Win32.

  4. Укажите имя для проекта, например MathFuncsDll, в поле Имя. Укажите имя для решения, например DynamicLibrary, в поле Имя решения. Нажмите кнопку ОК.

  5. На странице Обзор диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

  6. На странице Параметры приложения выберите в поле Тип приложения пункт DLL.

  7. Нажмите кнопку Готово, чтобы создать проект.

Добавление класса в библиотеку динамической компоновки

  1. Чтобы создать файл заголовка для нового класса, в меню Проект выберите пункт Добавить новый элемент. В диалоговом окне Добавить новый элемент в левой области в разделе Visual C++ выберите Код. В центральной области выберите Заголовочный файл (.h). Укажите имя для заголовка файла, например MathFuncsDll.h, и нажмите кнопку Добавить. Отобразится пустой файл заголовка.

  2. Добавьте следующий код в начало файла заголовка:

    1. C++
      // MathFuncsDll.h

#ifdef MATHFUNCSDLL_EXPORTS
#define MATHFUNCSDLL_API __declspec(dllexport)
#else
#define MATHFUNCSDLL_API __declspec(dllimport)
#endif

    2. Добавьте базовый класс с именем MyMathFuncs, выполняющий обычные арифметические операции, такие как сложение, вычитание, умножение и деление. Код должен выглядеть примерно следующим образом:

      C++
      namespace MathFuncs
{
    // This class is exported from the MathFuncsDll.dll
    class MyMathFuncs
    {
    public:
        // Returns a + b
        static MATHFUNCSDLL_API double Add(double a, double b);

        // Returns a - b
        static MATHFUNCSDLL_API double Subtract(double a, double b);

        // Returns a * b
        static MATHFUNCSDLL_API double Multiply(double a, double b);

        // Returns a / b
        // Throws const std::invalid_argument& if b is 0
        static MATHFUNCSDLL_API double Divide(double a, double b);
    };
}

    3. Когда символ MATHFUNCSDLL_EXPORTS определен, символ MATHFUNCSDLL_API установит модификатор __declspec(dllexport) в объявлениях функций-членов в этом коде. Этот модификатор разрешает экспорт функции библиотекой DLL для использования ее другими приложениями. Если символ MATHFUNCSDLL_EXPORTS не определен, например, когда файл заголовка включен приложением, символ MATHFUNCSDLL_API определяет модификатор __declspec(dllimport) в объявлениях функций-членов. Этот модификатор оптимизирует импорт функции в приложении. По умолчанию шаблон нового проекта для библиотеки DLL добавляет символ PROJECTNAME_EXPORTS в список определенных символов для проекта DLL. В этом примере символ MATHFUNCSDLL_EXPORTS определяется при сборке проекта MathFuncsDll. Для получения дополнительной информации см. dllexport, dllimport.

      1. Примечание

        Если проект DLL собирается в командной строке, воспользуйтесь параметром компилятора /D, чтобы определить символ MATHFUNCSDLL_EXPORTS.

      2. В проекте MathFuncsDll в обозревателе решений в папке Исходные файлы откройте файл MathFuncsDll.cpp.

      3. Реализуйте функциональность класса MyMathFuncs в исходном файле. Код должен выглядеть примерно следующим образом:

        C++
        // MathFuncsDll.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "MathFuncsDll.h"
#include <stdexcept>

using namespace std;

namespace MathFuncs
{
    double MyMathFuncs::Add(double a, double b)
    {
        return a + b;
    }

    double MyMathFuncs::Subtract(double a, double b)
    {
        return a - b;
    }

    double MyMathFuncs::Multiply(double a, double b)
    {
        return a * b;
    }

    double MyMathFuncs::Divide(double a, double b)
    {
        if (b == 0)
        {
            throw invalid_argument("b cannot be zero!");
        }

        return a / b;
    }
}

      4. Скомпилируйте библиотеку динамической компоновки, выбрав Собрать решение в меню Сборка.

        1. Примечание

          При использовании выпуска Express, в котором не отображается меню Сборка, в строке меню выберите Сервис, Параметры, Расширенные параметры, чтобы включить это меню, а затем выберите Сборка, Собрать решение.

          Примечание

          При сборке проекта из командной строки используйте параметр компилятора /LD, указывающий на то, что выходной файл должен являться DLL-файлом. Для получения дополнительной информации см. /MD, /MT, /LD (использование библиотеки времени выполнения). Используйте параметр компилятора /EHsc для включения обработки исключений С++. Для получения дополнительной информации см. Параметр /EH (модель обработки исключений).

        Создание приложения, ссылающегося на DLL

        1. Чтобы создать приложение С++, которое будет ссылаться и использовать созданную ранее библиотеку DLL, в меню Файл выберите пункт Создать и затем пункт Проект.

        2. В левой области в категории Visual C++ выберите Win32.

        3. В центральной области выберите Консольное приложение Win32.

        4. Укажите имя проекта, например MyExecRefsDll, в поле Имя. В раскрывающемся списке рядом с полем Решениевыберите Добавить в решение. В результате новый проект будет добавлен в решение, содержащее библиотеку DLL. Нажмите кнопку ОК.

        5. На странице Обзор диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

        6. На странице Параметры приложения выберите в поле Тип приложения пункт Консольное приложение.

        7. На странице Параметры приложения в разделе Дополнительные параметры снимите флажок Предкомпилированный заголовок.

        8. Нажмите кнопку Готово, чтобы создать проект.

          Использование функциональности из библиотеки классов в приложении

          1. После создания консольного приложения будет создана пустая программа. Имя исходного файла будет совпадать с ранее выбранным именем. В этом примере он имеет имя MyExecRefsDll.cpp.

          2. Для использования в приложении математических процедур, созданных в библиотеке DLL, необходимо сослаться на эту библиотеку. Для этого в обозревателе решений выберите проект MyExecRefsDll, а затем в меню Проект выберите пункт Ссылки. В диалоговом окне Страницы свойств разверните узел Общие свойства, выберите .NET Framework и ссылки и нажмите кнопку Добавить новую ссылку. Дополнительные сведения о диалоговом окне Ссылки см. в разделе Добавление ссылок.

          3. В диалоговом окне Добавление ссылки перечислены библиотеки, на которые можно создать ссылку. На вкладке Проект перечислены все проекты текущего решения и включенные в них библиотеки, если они есть. Установите флажок рядом с MathFuncsDll на вкладке Проекты, а затем нажмите кнопку ОК.

          4. Для создания ссылки на файлы заголовка DLL необходимо изменить путь к каталогам включения. Для этого в диалоговом окне Окна свойств последовательно разверните узлы Свойства конфигурации и C/C++, а затем выберите Общие. В поле Дополнительные каталоги включаемых файлов укажите путь к месту размещения файла заголовка MathFuncsDll.h. Можно использовать относительный путь, например ..\MathFuncsDll\. Затем нажмите кнопку ОК.

          5. Теперь класс MyMathFuncs можно использовать в приложении. Замените содержимое файла MyExecRefsDll.cpp следующим кодом.

            1. C++
              // MyExecRefsDll.cpp
// compile with: /EHsc /link MathFuncsDll.lib

#include <iostream>

#include "MathFuncsDll.h"

using namespace std;

int main()
{
    double a = 7.4;
    int b = 99;

    cout << "a + b = " <<
        MathFuncs::MyMathFuncs::Add(a, b) << endl;
    cout << "a - b = " <<
        MathFuncs::MyMathFuncs::Subtract(a, b) << endl;
    cout << "a * b = " <<
        MathFuncs::MyMathFuncs::Multiply(a, b) << endl;
    cout << "a / b = " <<
        MathFuncs::MyMathFuncs::Divide(a, b) << endl;

    try
    {
        cout << "a / 0 = " <<
            MathFuncs::MyMathFuncs::Divide(a, 0) << endl;
    }
    catch (const invalid_argument &e)
    {
        cout << "Caught exception: " << e.what() << endl;
    }

    return 0;
}

            2. Соберите исполняемый файл, выбрав команду Собрать решение в меню Сборка.

            Запуск приложения

            1. Убедитесь в том, что проект MyExecRefsDll выбран в качестве проекта по умолчанию. В обозревателе решенийвыберите MyExecRefsDll и затем в меню Проект выберите Назначить запускаемым проектом.

            2. Чтобы запустить проект, в строке меню выберите Отладка, Запуск без отладки. Результат выполнения должен выглядеть примерно следующим образом:

            a + b = 106,4
            a - b = -91,6
            a * b = 732,6
            a / b = 0,0747475
            Перехвачено исключение: b не может быть равно нулю!

            Данная страница с сайта Майкрософт ссылка - Сайт