Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » Borland C++ Builder - Приложения и распространение программ »

Обсудить на форуме Обсудить на форуме

Создаем DLL

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

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

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

Статическая загрузка

Создадим сперва проект (File / New / DLL). Будет создан проект, содержащий следующее:

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) {
    return 1;
	}

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

Для экспорта и импорта из DLL необходимо использовать моди фикаторы __export и __import соответсвенно. Но в C++ Builder можно использовать новое ключевое слово __delspec() с параметрами dllexport и dllimport соответсвенно. Сами понимаете, что для того чтобы эспортировать функции из библиотеки еужен один заголовочный файл с описаниями _delspec(dllexport) для экспортируемых функций, для импорта функций в приложение вам необходимо будет поставить анологичный заголовочный файл но с _delspec(dllimport) описаниями, что достаточно неудобно. Эта проблема решается легко: добавте в заголовочный файл библиотеки следующее:

#if defined(BUILD_DLL)
#  define DLL_EXP __declspec(dllexport)
#else
# if defined(BUILD_APP)
#  define DLL_EXP __declspec(dllimport)
# else
#  define DLL_EXP
# endif
#endif

в исходном файле DLL напишите #define BUILD_DLL, а вместо __declspec(dllexport) пишите DLL_EXP. При написании программы добавте строчку #define BUILD_APP, и просто подключите заголовочный файл DLL.

Пример DLL: файл P.cpp

//---------------------------------------------------------------------------
#define BUILD_DLL
#include 
#include "p.h"
#pragma hdrstop

//---------------------------------------------------------------------------
// Important note about DLL memory management when your DLL uses the
// static version of the RunTime Library:
//
// If your DLL exports any functions that pass String objects (or structs/
// classes containing nested Strings) as parameter or function results,
// you will need to add the library MEMMGR.LIB to both the DLL project and
// any other projects that use the DLL. You will also need to use MEMMGR.LIB
// if any other projects which use the DLL will be perfomring new or delete
// operations on any non-TObject-derived classes which are exported from the
// DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
// EXE's to use the BORLNDMM.DLL as their memory manager. In these cases,
// the file BORLNDMM.DLL should be deployed along with your DLL.
// To avoid using BORLNDMM.DLL, pass string information using "char *" or
// ShortString parameters.
//
// If your DLL uses the dynamic version of the RTL, you do not need to
// explicitly add MEMMGR.LIB as this will be done implicitly for you
//-------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) {
	return 1;
	}

//-------------------------------------------------------------------------

void Message(char *s) {
	i=10;
	Application->MessageBox(s,"From DLL",IDOK);
	}

Файл P.h

#if defined(BUILD_DLL)
# define DLL_EXP __declspec(dllexport)
#else
# if defined(BUILD_APP)
# define DLL_EXP __declspec(dllimport)
# else
# define DLL_EXP
# endif
#endif

DLL_EXP void Message(char *s);
DLL_EXP int i;

Скомпилируйте проект.

Если вы нажмете Run то после завершенния построения будет выдано сообщение что данная программа не можнт быть исполнена (естественно). Теперь напишем вызывающую программу. Втомже каталоге создайде новый проект (File / New Application) в форму поместите одну кнопку и создай обработчик события OnClick. Ваш исполняемый файл должен представлять собой слдующее:

//---------------------------------------------------------------------------
#include 
#define BUILD_APP
#pragma hdrstop
#include "p.h"
#include "Unit1.h"
#include 
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//-------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) {
    }

//-------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender) {
    char c[10];
    Message("roma");
    for( ; i>0;i--) {
        sprintf(c,"Example %d",i );
        Application->MessageBox("Example of using DLL variable",(char*)c,IDOK);
    }
}

//-------------------------------------------------------------------------

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

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

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

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

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

extern "C" void __export  Message(char *s)  {
	Application->MessageBox(s,"From DLL",IDOK);
	}

Cкомпилируйте проект, в результате чего будет создана DLL.

Теперь создайте проект приложения анологичный проекту для использования статической загрузки (форма с кнопкой и обработчиком события кнопки OnClick) ниже приведен код приложения:(Unit11.cpp)

//---------------------------------------------------------------------------
#include 
#pragma hdrstop
#include "Unit11.h"
#include 
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) {
	}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender) {
	void (__stdcall *Message)(char *s);
	HINSTANCE dllp = LoadLibrary("p.dll");
	if (dllp) {
		Message= (void(__stdcall *) (char*))
		GetProcAddress(dllp, "_Message");
		if (Message) Message("Hi From Dinamic DLL");
		}
	FreeLibrary(dllp);
	}
//---------------------------------------------------------------------------

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

  • void (__stdcall *Message)(char *s);-объявление указателя на функцию.
  • HINSTANCE dllp = LoadLibrary("p.dll");- загрузка библиотеки в память.
  • Message= (void(__stdcall *) (char*)) GetProcAddress(dllp, "_Message"); присвоение указателю адреса функции DLL.
  • Message("Hi From Dinamic DLL"); рабочий вызов фунциий (собственно то для чего все это и делается).
  • FreeLibrary(dllp);- выгрузка библиотеки из памяти.

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


следует следить что бы совпадали объявление функции в *.dll и *.exe (в частности в этом примере : __stdcall не совпадают, потому выкидывает External exception)
привожу рабочий пример:
void __fastcall TForm1::Button1Click(TObject *Sender) {
void (*Message)(char *s);
HINSTANCE dllp = LoadLibrary("p.dll");
if (dllp) {
Message= (void(*) (char*))
GetProcAddress(dllp, "_Message");
if (Message) Message("Hi From Dinamic DLL");
}
FreeLibrary(dllp);
}
dll-ку оставляем прежнюю

Содержимое параграфа "Сущность динамического связывания"

Статическая и динамическая компоновка

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

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

Рассмотрим сущность динамического и статического связывания (или компоновки)

·         Статическое связывание (static linking) имеет место в процессе связывания программы, когда для создания исполняемого exe-файла связываются воедино разные объектные obj-модули, lib-файлы библиотек и, как правило, скомпилированные res-файлы ресурсов.

·         Термин динамическое связывание (dynamic linking) относится к процессам, которые Windows использует для того, чтобы связать вызов функции в одном из модулей с реальной функцией из DLL. В отличие от статического связывания динамическое связывание имеет место во время выполнения программы. В выполняемый файл занесена только информация о местонахождении функций DLL (эту информацию приложение получает из так называемых библиотек импорта).

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

  • Некоторые динамически подключаемые библиотеки (например, файлы шрифтов) содержат только ресурсы. В них содержатся только данные (обычно в виде ресурсов), и практически нет программного кода.

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

Хотя модуль динамически подключаемой библиотеки может иметь любое расширение (.exe или .fon), стандартным расширением, принятым в Windows, является .dll.

  • Только те DLL, которые имеют расширение .dll, Windows загрузит автоматически. Если файл имеет другое расширение, то приложение должно загрузить модуль библиотеки явно. Для этого используется функция LoadLibrary или LoadLibraryEx.

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

·         Эти подпрограммы можно поместить в обычную объектную библиотеку (файл с расширением .lib) и добавить ее к каждому программному модулю при компоновке. Но такой подход избыточен, поскольку в этом случае в каждой программе будет находиться одинаковый код подпрограмм, выполняющих одни и те же задачи.

·         Более того, при изменении одной из подпрограмм в библиотеке, необходимо перекомпоновывать все программы, в состав которых входит эта подпрограмма.

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

Три значения одного слова “библиотека”

В предыдущем изложении слово “библиотека” встречается в разных контекстах. Помимо динамически подключаемых библиотек оно упоминается в сочетаниях “объектная библиотек” и “библиотека импорта”. Разберемся в этих понятиях.

Объектная библиотека – это файл с расширением .lib, в котором содержится код, добавляемый к коду программы, находящемуся в файле c расширением .exe, когда идет компоновка программы.

·         Например, в Microsoft Visual C++ обычная объектная библиотека времени выполнения, которая при компоновке присоединяется к программе, называется libc.lib.

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

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

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

Пример простой DLL

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

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

Поэтому,

  • Если необходимо подключить библиотеку на C к приложению на C++, все функции из этой библиотеки придется объявить как внешние в формате C. Это делается так:
 
        extern “C” int Function(int Param);
 
  • Если все функции библиотеки объявлены во включаемом файле MyLib.h, то модификатор extern можно применить к целому блоку, к которому с помощью директивы #include подключен файл заголовков С-функций из DLL:
 
        extern “C” 
        {
               #include “MyLib.h”
        }
 

Рассмотрим теперь процесс создания и использования DLL в самом легком варианте.

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

MyDLL.h
        #define EXPORT extern “C” __declspec (dllexport)
        EXPORT int CALLBACK MyFunction(char *str);
 
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 (ее библиотека импорта).

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

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

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

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

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

 
MyApp.c
        #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.

  • Поскольку код модулей приложений и динамически подключаемых библиотек защищен о записи, один и тот же код DLL может использоваться разными процессами. Однако данные, с которыми работает DLL – для каждого процесса свои. Каждый процесс имеет собственное адресное пространство для всех данных, которые использует DLL. Разделение памяти между процессами требует, как будет видно дальше, дополнительных усилий.
  • DLL – это не только расширение Windows, это еще и дополнение приложения, которое ее использует. Все, что делает динамически подключаемая библиотека, делается от имени приложения. Например, владельцем всей выделяемой библиотекой памяти является приложение. Владельцем всех создаваемых библиотекой окон является приложение. Владельцем всех открываемых библиотекой файлов является приложение.
  • Несколько приложений могут использовать одну и ту же динамически подключаемую библиотеку. Однако если написать динамически подключаемую библиотеку с функциями, которые могут вызваться из нескольких потоков программы, компилировать библиотеку следует с такими же опциями, как и многопоточное приложение.

Содержимое параграфа "Способы связывания приложения с DLL"

Рассмотрим различные способы связывания приложения с библиотекой динамической компоновки.

Связывание с DLL при загрузке приложения (неявное подключение)

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

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

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

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

Динамическая загрузка DLL в процессе работы приложения

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

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

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

 

        HINSTANCE hMyDll;
        . . .
        if((hMyDll=LoadLibrary(“MyDLL”))==NULL) 
        { 
                /* не удалось загрузить DLL */ 

        }
        else 
        { 
                /* приложение имеет право пользоваться функциями DLL через hMyDll */ 

        }
 

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

Рассмотрим процесс поиска файла библиотеки DLL операционной системой.

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

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

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

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

 

        typedef int (WINAPI *PFN_MyFunction)(char *);
        . . . 
        PFN_MyFunction pfnMyFunction; 
 

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

 

        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);
 

В заключение отметим следующее:

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

Содержимое параграфа "Некоторые важные аспекты создания DLL"

 

 

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

Назначение функции DllMain

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

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

 

        BOOL WINAPI DllMain(HINSTANCE hDLL,DWORD dwReason,LPVOID lpReserved)
        {       
                switch(dwReason)
                {
                       case DLL_PROCESS_ATTACH:       // инициализация процесса
                               break;
                       case DLL_THREAD_ATTACH:        // инициализация потока
                               break;
                        case DLL_ THREAD _DETACH:      // очистка структур потока
                               break;
                       case DLL_PROCESS_DETACH:       // очистка структур процесса
                               break;
                }
                return TRUE;
}
 

·         Первым параметром функции DllMain является дескриптор модуля библиотеки, для которого вызывается эта функция. Этот дескриптор можно сохранить в глобальной переменной и затем использовать для доступа к ресурсам модуля библиотеки. Последний параметр резервируется системой. Параметр dwReason может принимать одно из четырех значений, которое идентифицирует причину вызова функции DllMain операционной системой.

 Замечание. Перед продолжением изложения материала следует отметить, что одна и та же программа (приложение) может быть загружена несколько раз, и ее экземпляры работают как отдельные процесс.

Значение DLL_PROCESS_ATTACH параметра dwReason означает, что динамически подключаемая библиотека отображена в адресном пространстве процесса. Это сигнал библиотеке выполнить какие-то задачи по инициализации, которые требуются для обслуживания последующих запросов от процесса. Функция DllMain динамически подключаемой библиотеки вызывается с параметром DLL_PROCESS_ATTACH только один раз, при загрузке библиотеки в адресное пространство процесса (приложения).

·         Любой другой процесс, использующий ту же динамически подключаемую библиотеку, тоже приводит к новому вызову функции DllMain с параметром DLL_PROCESS_ATTACH, но это происходит от имени нового процесса.

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

Если параметр dwReason равен значению DLL_PROCESS_DETACH, то это означает, что динамически подключаемая библиотека больше процессу не нужна. Это дает возможность библиотеке освободить занимаемые ресурсы системы.

Аналогично, если функция DllMain вызывается с параметром DLL_THREAD_ATTACH, это означает, что связанный с библиотекой процесс создал новый поток. Когда поток завершается, Windows вызывает функцию DllMain с параметром DLL_THREAD_DETACH.

·          

 Замечание. Следует отметить, что существует вероятность того, что вызов функции DllMain с параметром DLL_THREAD_DETACH произойдет без предварительного ее вызова с параметром DLL_THREAD_ATTACH. Это возможно в том случае, если библиотека была загружена после создания потока.

·         Когда функция DllMain вызывается с параметром DLL_THREAD_DETACH, поток еще существует. Динамически подключаемая библиотека может даже посылать сообщения потоку в этот момент. Но при этом нельзя использовать функцию PostMessage, поскольку поток, вероятно, закончится еще до того, как сообщение, посланное этой функцией, будет обработано.

Итак,

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

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

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

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

Есть два способа занесения информации об этих функциях в такую таблицу на этапе компиляции.

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

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

Приведем пример создания DLL-библиотеки с использованием второго способа экспортирования функций.

Следующий def-файл содержит имя и описание библиотеки, а также список экспортируемых функций:

 

OtherMyDLL.def

        LIBRARY        “OtherMyDLL”

        DESCRIPTION    “OtherMyDLL – пример DLL-библиотеки

        EXPORTS
               MyFunction     @1
 

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

 Замечание. На самом деле компилятор присваивает номера всем экспортируемым объектам, даже если строка описана так

 

        MyFunction     
 

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

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

 

        MyFunction     @1 NONAME
 

  • Иногда это позволяет сэкономить много места в файле DLL.

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

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

 

OtherMyDLL.h

        extern “C” int CALLBACK MyFunction(char *str);
 

OtherMyDLL.c

        #include <windows.h>
        #include “OtherMyDLL.h”
 

        int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved)
        {
               return TRUE;
        }
        extern “C” int CALLBACK MyFunction(char *str)
        {
               MessageBox(NULL,str,”Function from DLL”,MB_OK);
               return 1;
        }
 

Разделяемая память в DLL

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

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

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

 

        #pragma data_seg (“.MySeg”)

        int sharedInts[5]={0,0,0,0,0}; // переменная общего пользования
        // другие переменные общего пользования

        . . .
        #pragma data_seg ()

        #pragma comment (lib, ”msvcrt” “-SECTION: .MySeg, rws”);

 

Все инициализированные переменные, объявленные между директивами #pragma data_seg (), размещаются в сегменте .MySeg. Важно специально инициализировать эти переменные, в противном случае компилятор помещает эти переменные вместо области памяти .MySeg в обычную неинициализируемую область.

·         Директива #pragma comment () – не обычный комментарий. Она дает указание библиотеке времени выполнения системы C пометить новый раздел как разрешенный для чтения, записи и совместного доступа (rws).

Некоторые ограничения DLL

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

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

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

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

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

Регистрация классов окна и создания окон в библиотеке может оказаться несколько сложнее. И для структуры класса окна, и для вызова функции CreateWindow требуется дескриптор экземпляра.

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

·         Поскольку сообщения для модальных окон диалога минуют очередь сообщений программы, с помощью вызова функции DialogBox можно создать в библиотеке модальное окно диалога. Дескриптор экземпляра можно взять из библиотеки, а параметр hwndParent функции DialogBox может быть задан равным NULL.


Может пригодится:


Автор:
Прочитано: 33552
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (11)

Прислал: C.
.

Прислал: C.
Абсолютно и совершенно бестолково. В духе всех разработчиков SDK, составляющих Help, которые и понятия не имеют о том, чем занимаются разработчики приложений и что им в конце концов нужно. Масса слов и нет ни одного нормального цельного и актуального

Прислал: C.
примера. Вы так ничего и не поняли и так ничему и не научились.

Прислал: Сергей
Сложно оценить качество материала.Жаль что строка предложного здесь решения: #pragma comment (lib, ”msvcrt” “-SECTION: .MySeg, rws”); не работает. В чем дело?

Прислал: Сергей
Сложно оценить качество материала.Жаль что строка предложенного здесь решения: #pragma comment (lib, ”msvcrt” “-SECTION: .MySeg, rws”); не работает. В чем дело?

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

Прислал: Константин
Да, написано походу для тех кому это уже не нужно(в силу их развития). На Delphi хук ставил WH_CBT, а тут скомпилить длл не могу(ну тоже хук). Придётся дальше интернет лопатить, только другие материалы ещё более скудные

Прислал: Константин
Уже пожалел что с С++ связался, думал будет удобней с msdn работать. На Delphi к некоторым функциям вечно приходилось оболочки искать. Но С++ это какая то запара

Прислал: Антон
страшная халтура.... Ошибки даже не на уровне логики изложения, а уже в опечатках, по три на предложение.

Прислал: Сергей
В общем все это работает четко! проверено на практике уже давно. Поначалу я тоже долго бился не получалось и не работало, теперь любой код с длл пишу, учите МАТЬ-часть!!!

Прислал: Боря
Все понятно, но хоте бы попросить расказать о экспорте\импорте классов с DLL.

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру