45831 (Реализация отложенной загрузки библиотек на С++)

2016-07-31СтудИзба

Описание файла

Документ из архива "Реализация отложенной загрузки библиотек на С++", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.

Онлайн просмотр документа "45831"

Текст из документа "45831"

Реализация отложенной загрузки библиотек на С++

Андрей Солодовников

Вы все еще грузите библиотеки вручную?

Тогда мы идем к вам!

Краткая предыстория

По специфике моей работы мне довольно часто приходится вручную загружать библиотеки и динамически, при помощи GetProcAddress, импортировать множество функций. Это происходит отчасти потому, что требуется обеспечить совместимость с различными версиями Windows, в которых целевые функции могут отсутствовать, отчасти потому, что так бывает удобнее (например, при реализации механизма плагинов). Конечно, всегда хочется это автоматизировать, особенно если функций и библиотек много. С одной стороны, в линейке Visual C++ для этого есть поддержка компилятора\линкера в виде механизма Delay Load, с другой стороны, бытует мнение, что использовать этот метод является дурным тоном, и, наверное, это так. Одна из основных причин, которую хочется отметить особенно – этот механизм является microsoft-specific, то есть никаких гарантий, что написанный Вами код будет работать и на других компиляторах или платформах, нет. Более того, несколько раз «попав» на странное поведение этого механизма (например, см. Q218613), мы от его использования в своих проектах отказались.

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

ПРИМЕЧАНИЕ

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

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

Все это, вкупе с потраченным временем, сподвигло меня к необходимости написания очередного велосипеда в виде библиотеки эмуляции Delay Load, а также и этой статьи.

Требования к библиотеке, реализующей механизм Delay load

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

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

Как можно большая независимость от компилятора С++ (в пределах ANSI C++). Минимальные требования к компилятору – библиотека должна быть полностью функциональна на всех Visual C++ компиляторах, начиная с Visual C++ 6.0;

Минимальное количество кода, генерируемого компилятором, которое приходится на одну импортируемую функцию;

Удобство определения в проекте импортируемых библиотек\функций;

Возможность задания своих стратегий (реакций) на ошибки загрузки библиотеки\нахождения функции;

Минимизация вызовов LoadLibrary. Для одной библиотеки (модуля) вызов LoadLibrary должен производится один раз вне зависимости от количества импортируемых из нее функций. Данный механизм должен работать не только в пределах одной единицы трансляции, но и проекта в целом. Таким образом, должна создаваться единая для приложения таблица используемых модулей;

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

Библиотека должна обеспечивать привычный синтаксис вызова – не должно быть никаких внешних отличий от обычного вызова функции из С/С++;

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

Из описанных выше требований наиболее важными и интересными представляются пункты 3,5,6 и 7. Особенности их реализации будут рассмотрены более подробно далее вместе с программной реализацией библиотеки. Для тех, кому детали реализации не интересны, а интересны методы использования библиотеки, предлагается приступать сразу к разделу Использование библиотеки.

Предлагаемая реализация библиотеки

Класс, инкапсулирующий работу с модулями

Для начала рассмотрим требование (5) к реализации загрузки библиотек\модулей. Очевидно, что обеспечить выполнение данного требования с условием уникальности экземпляра библиотеки в пределах всех единиц трансляции проекта можно использованием паттерна Singlton для загружаемого модуля. При этом для каждого различного загружаемого модуля должен создаваться собственный экземпляр синглтона, который и будет обеспечивать «одноразовую» загрузку в конструкторе и в дальнейшем выгрузку библиотеки в деструкторе. Эта задача решается определением шаблонного класса CModule. Имя библиотеки должно служить значением, относительно которого производится инстанцирование объекта, инкапсулирующего загрузку библиотеки. Поскольку в качестве паттерна Singlton используется синглтон Мейерса, то в качестве бонуса мы получаем отложенную загрузку библиотек (поскольку создание экземпляра синглтона производится при первом обращении к порождающей функции).

СОВЕТ

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

template

struct CMeyersSinglton

{

static T& GetInstance()

{

static T obj;

return obj;

}

};

В связи с этим первый вариант определения шаблона CModule мог бы выглядеть так:

template

class CModule;

Тут следует сделать небольшое отступление. Как было бы прекрасно, если бы любой абстрактный язык программирования, используемый нами, обеспечивал бы любую востребованную нами возможность. Но, очевидно, по соображениям здравого смысла, это невыполнимо, поэтому приходится пользоваться тем, что есть. А есть такая неприятная вещь – в С++ напрямую инстанцировать шаблон строковым литералом не получится. Шаблон может быть инстанцирован только константой с external linkage, а строковый литерал имеет internal linkage. На первый взгляд, все достаточно печально. Однако, как обычно, решение лежит на поверхности. Оно очень простое и очевидное. Мы будем инстанцировать шаблон модуля уникальным классом, инкапсулирующим строковый литерал. Сам же класс будет формироваться при помощи макросов:

#define DECLARE_NAME_ID_IMPL(id, name, ret, text)\

struct NAME_ID(id)\

{\

enum {length = sizeof(name)};\

static ret GetStr(){return text(name);}\

};

#define DECLARE_NAME_ID_A(id, name) DECLARE_NAME_ID_IMPL(id, name, LPCSTR, DL_EMPTY())

#define DECLARE_NAME_ID(id, name) DECLARE_NAME_ID_IMPL(id, name, LPCTSTR,_T)

Данный класс является универсальным и будет использован в дальнейшем и для представления имен импортируемых функций. Но и тут есть один маленький нюанс – поскольку функция GetProcAddress использует только ANSI строки, то мы вынуждены это предусмотреть, объявив дополнительный макрос DECLARE_NAME_ID_A.

Итак, в связи со всем вышеизложенным, определение шаблона CModule без учета стратегий будет выглядеть так:

template

class CModule;

Теперь добавим стратегии загрузки\выгрузки модуля. Поскольку стратегия контролирует процессы, связанные с загрузкой и выгрузкой, у нее должно быть как минимум 2 функции. Одна отвечает за загрузку модуля, вторая за его выгрузку:

struct CModulePolicy

{

static HMODULE Load(LPCTSTR szFileName);

static BOOL Free(HMODULE hModule);

};

Теперь у нас есть все, что необходимо для полного написания класса CModule. Реализация его в предлагаемой библиотеке приведена в листинге ниже:

struct CModuleLoadLibraryPolicy

{

static HMODULE Load(LPCTSTR szFileName)

{

return ::LoadLibrary(szFileName);

}

static BOOL Free(HMODULE hModule)

{

return ::FreeLibrary(hModule);

}

};

struct CModuleGetModuleHandlePolicy

{

static HMODULE Load(LPCTSTR szFileName)

{

return ::GetModuleHandle(szFileName);

}

static BOOL Free(HMODULE hModule)

{

return TRUE;

}

};

template

class CModule

{

public:

typedef CModule type;

typedef Name name_type;

static type &GetModule()

{

#ifdef DL_MT

static volatile LONG lMutex = FALSE;

CLWMutex theMutex(lMutex);

CAutoLock autoLock(theMutex);

#endif //DL_MT

static type Module;

return Module;

}

HMODULE GetModuleHandle() const

{

return m_hModule;

}

BOOL IsLoaded() const

{

return m_hModule != NULL;

}

// Caution - use with care. Not thread-safe

BOOL UnloadModule()

{

HMODULE hModule = m_hModule;

m_hModule = NULL;

return LoadPolicy::Free(hModule);

}

~CModule()

{

if (m_hModule)

UnloadModule();

}

private:

CModule()

{

m_hModule = LoadPolicy::Load(name_type::GetStr());

}

HMODULE m_hModule;

};

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

Реализация динамического поиска функций и глобальной таблицы импорта

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

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

template

struct CGlobalProxyTable

{

static FARPROC &GetProxy()

{

static FARPROC proxy;

return proxy;

}

};

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

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

template

class CDynFunction

Учитывая все вышесказанное, реализация класса тривиальна и будет выглядеть так:

template

class CDynFunction

{

public:

typedef CDynFunction type;

typedef Proxy proxy_type;

typedef Module module_type;

typedef Name name_type;

static typename proxy_type::fun_type &GetProxy()

{

static typename proxy_type::fun_type proxy = proxy_type::template Proxy::ProxyFun;

return proxy;

}

static BOOL InitFunction()

{

#ifdef DL_MT

static volatile LONG lMutex = FALSE;

#endif // DL_MT

const module_type &theModule = module_type::GetModule();

if (theModule.IsLoaded())

return DL_GetProcAddressImpl(

#ifdef DL_MT

lMutex,

(const FARPROC)proxy_type::template Proxy::ProxyFun,

#endif //DL_MT

(volatile FARPROC &)GetProxy(),

theModule.GetModuleHandle(),

name_type::GetStr()

);

return FALSE;

}

};

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

Свежие статьи
Популярно сейчас
Как Вы думаете, сколько людей до Вас делали точно такое же задание? 99% студентов выполняют точно такие же задания, как и их предшественники год назад. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5137
Авторов
на СтудИзбе
440
Средний доход
с одного платного файла
Обучение Подробнее